Question

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?

Was it helpful?

Solution

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

  • Haskell/SML are functional languages, whereas JS is (mostly) an imperative language.
  • Haskell/SML use strict, static type systems, whereas JS uses a dynamic type system.
  • Haskell/SML (mostly) prevent side effects to variables by design. JS does not.

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');
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top