coffeescriptlivescript

CoffeeScript / LiveScript and data constructors


I have few experience with SML. Now I want to learn LiveScript, but I'm stuck at data types. So, is possible to create type using data constructors like in SML / Haskell / OCaml? If not, what is the preferred way to create data types?


Solution

  • The main differences between Haskell/SML and JavaScript/LiveScript/CoffeeScript are that:

    In these functional languages, data type definitions are used for compile-time type checking - JS's dynamic type system does its type checking at runtime, and so does not need to know the exact structure of objects. As a result, there is no direct translation of a data type definition.

    1. Just Use Objects

    If you just want to define a one-use data structure in your program, just instantiate a new object and give it some properties:

    // Create a new object with properties foo and bar
    var MyThing = {
        foo: 'a',
        bar: 'b'
    };
    
    // Set the baz property of our new object
    MyThing.baz = 'c';
    
    // Remove the foo property from our object
    delete MyThing.foo;
    

    This is almost identical in LiveScript, except less syntax-heavy:

    MyThing = { foo: \a, bar: \b }
    MyThing.baz = \c
    delete MyThing.foo
    

    2. Use JS prototypes / LiveScript classes

    If you are dealing with many instances of an object, or anything more simple than just defining a one-use object, you will probably want to use object prototypes. In JavaScript, all objects have a prototype object from which they are based. When you invoke the new operator on a constructor function, you get a copy of the function's prototype back, which is used as the this context for the constructor. For example:

    // Define our constructor
    var Thing = function(foo, bar) {
        this.foo = foo;
        this.bar = bar;
    };
    
    // Set a 'default' baz property for all Things
    Thing.prototype.baz = 'c';
    
    // Create a Thing
    var myThing = new Thing('a', 'b');
    
    // Inspect our properties
    console.log(myThing.foo, myThing.bar, myThing.baz) // => 'a', 'b', 'c'
    

    This can be represented directly in LiveScript:

    # Define a constructor
    Thing = (foo, bar) ->
        @foo = foo
        @bar = bar
    
    # Set a 'default' baz property for all Things
    Thing::baz = \c
    
    # Create a new Thing
    my-thing = new Thing \a \b
    
    # Inspect our properties
    console.log myThing.foo, myThing.bar, myThing.baz
    

    Or, more concisely, using the class syntax which represents (almost exactly) the same thing:

    class Thing
        (@foo, @bar) ->
        baz: \c
    
    my-thing = new Thing \a \b
    

    3. Immutable Data Structures

    If you're coming from Haskell or SML, you'll be familiar with the idea of immutability, and writing functions which cannot perform side effects.

    In the original example, we declared our object, myThing, which we then mutated. In JS, where objects are passed into functions by reference, side-effects can make it difficult to reason about where something is going wrong without using a debugger.

    To circumvent that, we can use the Immutable.js library, which provides immutable data structures such as Maps (essentially objects) and Lists (essentially arrays).

    Here is the original example, rewritten using an Immutable.js Map.

    // Create a new Map (like a standard JS object, but immutable)
    var myThing = Immutable.Map({ foo: 'a', bar: 'b' });
    
    /*
     * Set the baz property of our new Map
     * Note that the original `myThing` object remains unchanged,
     * because `myThing.set` returns a new Map with the changes supplied to set
     */
    var myThingWithBaz = myThing.set('baz', 'c');
    
    var myThingWithBazButNotFoo = myThingWithBaz.delete('foo');