How to make mutually recursive structures in Javascript?

223 views Asked by At

I was wondering if it is possible to have mutually recursive objects in Javascript and, if so, how?

Goal:

I want to have three objects:

  1. One that represents Boolean type with two values True and False
  2. One that represents a True object of Boolean type
  3. One that represents a False object of Boolean type

The trick is that I want to ask the True object its type and I should get back Boolean object and I want to ask a Boolean object its values and I should get back 2 objects: the True object and the False object.

But it should be totally be mutually recursive in the sense that I get something like this (though it doesn't necessarily have to be exactly like this):

True 
// {name : "True", type : [Object object]}

False
// {name : "False", type : [Object object]}

Boolean 
// {name : "Boolean", values : [Object object]}

Boolean.values  
// {True: [Object object], False: [Object object]}

True.type
// {name : "Boolean", values : [Object object]}

False.type
// {name : "Boolean", values : [Object object]}

Boolean.values.True 
// {name : "True", type: [Object object]}

Boolean.values.True.type 
// {name : "Boolean", values : [Object object]}

Boolean.values.True.type.values 
// {True : [Object object], False: [Object object]}

and so on...

If it helps, they should satisfy the properties that:

Boolean === Boolean.values.True.type 
Boolean === Boolean.values.True.type.values.True.type

True === Boolean.values.True
True === True.type.values.True.type.values.True.type.values.True

False === Boolean.values.False
False === True.type.values.False

and the ability to do this should be infinite

Note

These could be functions instead of objects. And the calls don't have to be exactly like this.

2

There are 2 answers

5
Zenorbi On BEST ANSWER

Here you go:

//Define the top level objects but avoid recursion
var True = {};
var False = {};
var Boolean = {
    values: {
        True: True,
        False: False
    }
};

//Create the recursion
True.type = Boolean;
False.type = Boolean;
3
Aadit M Shah On

That's very simple:

var Boolean = {
    name: "Boolean",
    values: {
        True: {
            name: "True"
        },
        False: {
            name: "False"
        }
    }
};

var True = Boolean.values.True;

var False = Boolean.values.False;

True.type = Boolean;

False.type = Boolean;

Are you trying to create an algebraic data type?


Edit: This is how I would create an algebraic data type:

function data(constructors) {
    var factory = function (constructor) {
        this.constructor = constructor || this;
    };

    var type = factory.prototype = {};

    for (var name in constructors) {
        var fields = constructors[name];

        if (fields) {
            var body = ["    var data = new " + name + "(arguments.callee);"];
            var length = fields.length;
            var params = [];

            for (var i = 0; i < length; i++) {
                var param = "arg" + i;
                body.push("    data." + fields[i] + " = " + param + ";");
                params.push(param);
            }

            body.unshift("return function (" + params.join(", ") + ") {");
            body.push("    return data;", "};");

            type[name] = Function(name, body.join("\n"))(factory);
        } else type[name] = new factory;
    }

    return type;
}

Using the data function we can define algebraic data types as follows:

var Boolean = data({
    True: null,
    False: null
});

var True = Boolean.True;
var False = Boolean.False;

var List = data({
    Nil: null,
    Cons: ["head", "tail"]
});

var Nil = List.Nil;
var Cons = List.Cons;

It has the following invariants:

Object.getPrototypeOf(True) === Boolean;
Object.getPrototypeOf(False) === Boolean;

Object.getPrototypeOf(Nil) === List;
Object.getPrototypeOf(Cons(0, Nil)) === List;

True.constructor === True;
False.constructor === False;

Nil.constructor === Nil;
Cons(0, Nil).constructor === Cons;

Using this, you can create pure functions as follows:

List.map = function (f) {
    switch (this.constructor) {
    case Nil: return Nil;
    case Cons:
        var x = this.head;
        var xs = this.tail;
        return Cons(f(x), xs.map(f));
    }
};

function map(f, a) {
    return a.map(f);
}

You can use it as follows:

function toList(a) {
    var list = Nil;
    for (var i = a.length - 1; i >= 0; i--) list = Cons(a[i], list);
    return list;
}

var xs = toList([1,2,3]);

var ys = map(function (a) {
    return a * 2;
}, xs);

Hope that helps.