Как сделать взаимно рекурсивные структуры в Javascript?

Мне было интересно, возможно ли иметь взаимно рекурсивные объекты в Javascript и, если да, то как?

Цель:

Я хочу иметь три объекта:

  1. Тот, который представляет Boolean тип с двумя значениями True а также False
  2. Тот, который представляет True объект Boolean тип
  3. Тот, который представляет False объект Boolean тип

Хитрость в том, что я хочу спросить True возражать его тип, и я должен вернуться Boolean объект, и я хочу спросить Boolean возражать его значения, и я должен получить 2 объекта: True объект и False объект.

Но он должен быть полностью рекурсивным в том смысле, что я получаю что-то вроде этого (хотя это не обязательно должно быть именно так):

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]}

и так далее...

Если это помогает, они должны удовлетворять свойствам, которые:

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

и способность делать это должна быть бесконечной

Заметка

Это могут быть функции, а не объекты. И звонки не обязательно должны быть такими.

2 ответа

Решение

Ну вот:

//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;

Это очень просто:

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;

Вы пытаетесь создать алгебраический тип данных?


Изменить: Вот как я бы создать алгебраический тип данных:

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;
}

Используя функцию данных, мы можем определить алгебраические типы данных следующим образом:

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;

Имеет следующие инварианты:

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;

Используя это, вы можете создавать чистые функции следующим образом:

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);
}

Вы можете использовать его следующим образом:

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);

Надеюсь, это поможет.

Другие вопросы по тегам