4

How do I construct functional and recursive datatypes in javascript?

I would like to be able to do something ML like:

datatype binary_node = Node of binary_node*binary_node 
                     | Lead of int

Some time ago I took a course in functional programming -- the course was, for some random reason, in Scheme, and we constructed datatypes by making tubles, starting with the name of the data type and then the 'payload', is this the way to do functional programming-style data types in Javascript?

construct_node(n1,n2) -> 
    ("Node", n1, n2).

construct_leaf(int_value) ->
    ("Leaf", int_value).

and then a type checker:

is_node(n) ->
    if (n[0] == "Node") ->
        is_binary_tree(n[1])  and is_binary_tree(n[2])
    else
        false
is_leaf(l) ->
    if(l[0] == "Leaf") ->
        is_integer(n[1])
    else 
        false
is_binary_tree(t) ->
    is_node(t) or is_leaf(t)

What would be the smartest way to do this in javascript?

3 Answers 3

2

JavaScript is commonly used in a duck typed fashion. In this way, you don’t need to define any special datatypes. Any object that has properties node1 and node2 may be considered a binary tree node.

var n = {
    node1: {
        node1: 456,
        node2: 789
    },
    node2: 1002
};

function isNode(x) {
    return x && isBinaryTree(x.node1) && isBinaryTree(x.node2);
}

function isLeaf(x) {
    return typeof x === 'number';
}

function isBinaryTree(x) {
    return isNode(x) || isLeaf(x);
}

Note that the functions above are for recursively checking the integrity of an entire tree, not for node-by-node case distinction during traversal.

Sign up to request clarification or add additional context in comments.

11 Comments

The costs of your predicates are linear in the size of the tree, which will make a traversal where you check at every node cost exponentially. (Also, I recommend avoiding meaningless terms like "duck typing".)
I like the term duck-type, I will, in the future, use it in papers :-)
@AndreasRossberg I’m not sure why you would want to check every node of a tree while traversing that same tree. Can you give a practical example? Also, I disagree about “duck typing”—I believe it is quite a meaningful (if not very precise) term—but I would be happy to hear your suggestions on terminology.
@MartinKristiansen You just ignore that extra node property, just like you ignore built-in properties like toString. So long as it has node1 and node2, it’s a binary tree node.
Note that I (and you) was talking about duck typing as a language property, not a use pattern. (I'd also disagree with custom properties or instanceof implementing "types", since both can give plenty of false positives.)
|
1

One of the ways I have seen is to create a constructor function for each case of the algebraic data type and use instanceof tests to implement the branching. For example, this is how the Roy language implements pattern matching and tagged unions.

var Node = function(a, b){
    //protect against people forgetting to use "new" to call the constructor:
    if(!(this instanceof Node)){ return new Node(a, b) }

    //optionaly do extra type checking, if that is your thing:
    if(!(a instanceof Leaf)){ /*...*/ }
    if(!(b instanceof Leaf)){ /*...*/ }

    //save properties
    this.node1 = a;
    this.node2 = b;
};

var Leaf = function(value){
    /*...*/
    this.value = value;
};

This way, the tag the the internal "__proto__" property, and the payload are the usual instance proeprties in the object.

A reason I like this approach is that it is very "type safe". The internal prototype property is not editable and using a constructor or object (instead of a symbol or string) makes it less subject to name clashes, either with different types or with properties of your object.

Another good point is that, unlike depending on duck typing, this approach also seamlessly works with enums and other situations where the different cases have the same set of properties.

A bad thing about Javascript is that, as in the LISP case, it does not have built in support for destructuring so you might want to make a custom matching function like the following one.

var match_tree = function(x, cases){
    //just a helper function to make things pretty
    if(x instanceof Node){
        return cases.node.call(this, x.node1, x.node2);
    }else if(x instanceof Leaf){
        return cases.leaf.call(this, x.value);
    }else{
        throw new TypeError("pattern match failed");
    }
};

var sum_leaves = function(tree){
    return match_tree(tree, {
        node: function(val){ return val },
        leaf: function(left, right){
           return sum_leaves(left) + sum_leaves(right);
        }
    });
 };

Comments

1

I'm the creator of a language called Roy. Roy is a language with static types, algebraic data types and pattern matching. It also compiles to JavaScript.

Your example would look something like this:

data Tree = Node Tree Tree | Leaf Number

Number is a built-in JavaScript type.

Now we can pattern match on the ADT:

let isNode n = match n
  case (Node a b) = true
  case (Leaf l) = false

let isLeaf l = match l
  case (Leaf l) = true
  case (Node a b) = false

Here's the JavaScript output:

var Node = function(Tree_0, Tree_1) {
    if(!(this instanceof Node)) {
        return new Node(Tree_0, Tree_1);
    }
    this._0 = Tree_0;
    this._1 = Tree_1;
};
var Leaf = function(Number_0) {
    if(!(this instanceof Leaf)) {
        return new Leaf(Number_0);
    }
    this._0 = Number_0;
};
var isNode = function(n) {
    return (function() {
        if(n instanceof Node) {
            var a = n._0;
            var b = n._1;
            return true;
        } else if(n instanceof Leaf) {
            var l = n._0;
            return false;
        }
    })();
};
var isLeaf = function(l) {
    return (function() {
        if(l instanceof Leaf) {
            var l = l._0;
            return true;
        } else if(l instanceof Node) {
            var a = l._0;
            var b = l._1;
            return false;
        }
    })();
};

4 Comments

My friend Kasper told me about your Roy, something about Monads and do-notation -- but Roy is a language that compiles down to javascript?
Yeah, it's specifically designed to compile to JS
Does it compile to other languages? ie. Serverside languages?
There was a fork that compiled Roy into Ruby. Not actively maintained but I've got some ideas on how to make it easy for Roy to support more targets.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.