Getting Started¶
Structs¶
The first important idea in Basilisk is the struct. A struct is an object that has a with_ method.
var joe = new Person({ name: 'Joe' });
// Properties can be accessed directly.
console.log('basic value', joe.name);
// You can derive a new value using 'with_'
console.log('adjusted', joe.with_('name', 'Joe Bloggs').name);
// The original value is - of course - not modified by deriving a new value.
console.log('original value', joe.name);
An easy way to make constructors for structs is the makeStruct function.
var Person = basilisk.makeStruct(['name', 'age']),
joe = new Person({ name: 'Joe' });
You can of course extend the prototype with any additional methods you like
Person.prototype.toString = function () {
return this.name + ' (' + this.age + ')';
}
console.log('I am ' + joe);
One additional method is created for you automatically: .equals(). We’ll come back to that in a little bit.
Collections¶
In a huge number of cases, you don’t need to create your own structures to represent data: lists and maps will do just fine. Basilisk comes with these essential data structures in a persistent [1] and performant form.
// Vectors have fast append, and fast random access (including set)
var numbers = basilisk.Vector.from([]),
numbers = numbers.push(5),
numbers = numbers.push(6),
numbers = numbers.push(7);
console.log(numbers.get(1));
numbers = numbers.set(1, 10);
// .length is O(1)
console.log(numbers.length);
The StringMap class gives a simple, safe map from strings to any value.
var students = basilisk.StringMap.from({});
students = students.set('Joe', 'Joe Bloggs');
console.log(students.get('Joe'));
// .get accepts a default (undefined is the default value)
console.log(students.get('Mary', 'not present'));
// Unlike normal javascript objects, StringMaps are safe for any string.
students.set('__proto__', 'Does not break the object');
The HashMap class is a more flexible mapping object - see its detailed documentation for more info.
Query¶
Creating immutable objects in Javascript is actually very easy: just use Object.freeze. However, deriving new versions of complex objects in a way which is both fast and easy to read is more of a challenge. Basilisk has a query module to make this easier. We often bind this to b$ to make our code a little clearer.
var b$ = basilisk.query,
example = make_a_complex_object();
console.log('Deep access:', example.deep.prop);
// changing 'prop' to be a new value would involve 'backward' reasoning
// in most environments:
example = example.with_('deep', example.deep.with_('prop', 5));
// with basilisk structs, this is clearer:
console.log(b$.replace(example, ['deep', 'prop'], 5));
// Where you are modifying more properties, or deriving a changed value
// use swap
console.log(b$.swap(example, ['deep', 'position'], function (current) {
return current
.with_('x', current.x + 5)
.with_('y', current.y + 10);
}));
// the second parameter (the path) can include more intelligent matchers
console.log(b$.replace(example, ['deep', b$.at(5)], 'hello'));
// basilisk.query.at() will handle any collection which uses .get and .set
// - so Vector, HashMap, and StringMap at the very least.
Equality¶
Probably the most useful thing about working with value objects is that strict equality (===) means that the objects and their children are exactly the same - and the check is incredibly quick.
There are many situations, however, where you want to check if two objects are the same: for this, basilisk supports .equals.
var personA = new Person({ name: 'Joe' }),
personB = new Person({ name: 'Mary' }).with_('name', 'Joe');
console.log('Not the same: ', personA === personB);
// however, it *is* valuable to know if they are identical:
basilisk.equals(personA, personB); // returns true.
Footnotes
[1] | a persistent data structure is a data structure that always preserves the previous version of itself when it is modified. |