2013-12-22 3 views
14

Теперь, когда node.js поддерживает ECMAScript Harmony generators мы можем написать одноместный код лаконично ала do блоков в Haskell:Реализация монада в JavaScript

function monad(unit, bind) { 
    return function (f) { 
     return function() { 
      var g = f.apply(this, arguments); 

      return typeOf(g) === "Generator" ? send() : unit(g); 

      function send(value) { 
       var result = g.next(value); 
       if (result.done) return unit(result.value); 
       else return bind(result.value, send); 
      } 
     }; 
    }; 
} 

function typeOf(value) { 
    return Object.prototype.toString.call(value).slice(8, -1); 
} 

В приведенном выше monad коде есть функция, которая может быть использована для создания детерминированных монады как :

var maybe = monad(function (a) { 
    return {just: a}; 
}, function (m, f) { 
    return m === null ? null : f(m.just); 
}); 

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

var readZip = maybe(function * (a, b) { 
    var a = yield readList(a); 
    var b = yield readList(b); 
    return _.zip(a, b); 
}); 

Вышеуказанная функция readZip берет две строки, преобразует их в списки, а затем застегивает их молнией. Если произошла ошибка, она немедленно возвращает null. Это зависит от следующих функций:

function readList(string) { 
    try { 
     var value = JSON.parse(string); 
     return value instanceof Array ? {just: value} : null; 
    } catch (error) { 
     return null; 
    } 
} 

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

console.log(readZip('[1,2,3,4]', '["a","b"]')); // [[1,"a"],[2,"b"],[3,"c"]] 
console.log(readZip('hello', '["a","b"]'));  // null 
console.log(readZip('[1,2,3,4]', 'world'));  // null 

Аналогично мы можем создать любые другие детерминированные монады. Например, мой фаворит, cont монада:

var cont = monad(function (a) { 
    return function (k) { 
     return k(a); 
    }; 
}, function (m, k) { 
    return function (c) { 
     return m(function (a) { 
      return k(a)(c); 
     }); 
    }; 
}); 

Теперь мы можем использовать cont для создания функций в продолжение прохождения стиль лаконично:

var fib = cont(function * (n) { 
    switch (n) { 
    case 0: return 0; 
    case 1: return 1; 
    default: 
     var x = yield fib(n - 1); 
     var y = yield fib(n - 2); 
     return x + y; 
    } 
}); 

Вы можете использовать функцию fib следующим образом:

fib(10)(function (a) { console.log(a); }); // 55 

К сожалению, monad работает только для детерминированных монадов. Это не работает для недетерминированных монад, таких как монашка list, потому что вы можете только возобновить генератор из определенной позиции один раз.

Итак, мой вопрос заключается в следующем: существует ли какой-либо другой способ реализации недетерминированных монадов, таких как монашка list, в JavaScript?

+0

Shameless вилка: https://github.com/elclanrs/doM.js – elclanrs

+0

@ elclanrs Это обман. Он работает, но поскольку вы создаете совершенно новую функцию, нет возможности сохранить лексическую среду исходной функции на сайте вызова. –

+0

Что ты имеешь в виду? Не могли бы вы уточнить? – elclanrs

ответ

7

Так что мой вопрос заключается в следующем: есть ли другой способ реализации недетерминированных монад как список монада сжато в JavaScript?

Я предлагаю эту реализацию монады, что я обратился к различным монадам here:

var extend = function(a, b) { 
    for (var i in b) 
    a[i] = b[i]; 
    return a; 
}; 

// Chain a new `this` 
var fluent = function(f) { 
    return function() { 
    var clone = extend(Object.create(null), this); 
    f.apply(clone, arguments); 
    return clone; 
    }; 
}; 

var toArray = function(x) { 
    return Array.prototype.slice.call(x); 
}; 

var List = { 
    unit: fluent(function() { 
    this.x = toArray(arguments); 
    }), 
    bind: function(f) { 
    var fx = this.x.map(f.bind(this)); 
    var a = fx[0]; 
    for (var i=1; i<fx.length; i++) 
     a.x = a.x.concat(fx[i].x); 
    return a; 
    }, 
    lift: function(f) { 
    return function(x) { 
     return List.unit(f(x)); 
    } 
    }, 
    valueOf: function() { 
    return this.x; 
    } 
}; 

var add1 = function(x) { 
    return x + 1; 
}; 

// Laws 
var m = List.unit(3); 
var f = List.lift(add1); 

var laws = [ 
    m.bind(f)[0] == f(3)[0], 
    m.bind(function(x){ return List.unit(x) })[0] == m[0], 
    m.bind(function(x){ return f(x).bind(f) })[0] == m.bind(f).bind(f)[0] 
]; 

console.log(laws); //=> [true, true, true] 

// lift 
var result = List.unit(1,2).bind(List.lift(add1)); //=> [2,3] 

console.log(result.valueOf()); 

// do 
var result = List.unit(1,2).bind(function(x) { 
    return this.unit(3,4).bind(function(y) { 
    return this.unit(x + y); 
    }); 
}); 

console.log(result.valueOf()); //=> [4,5,5,6] 

Очевидно, что «делать» синтаксис приводит к обратному вызову в ад, но в LiveScript вы можете облегчить боль:

result = do 
    x <- List.unit 1 2 .bind 
    y <- @unit 3 4 .bind 
    @unit x + y 

Вы также могли бы назвать ваш метод bind творчески:

result = do 
    x <- List.unit 1 2 .\>= 
    y <- @unit 3 4 .\>= 
    @unit x + y 
0

Там, кажется, отличный способ реализовать список монады как это:

function* unit(value) { 
    yield value; 
} 
function* bind(list, transform) { 
    for (var item of list) { 
     yield* transform(item); 
    } 
} 
var result = bind(['a', 'b', 'c'], function (element) { 
    return bind([1, 2, 3], function* (element2) { 
     yield element + element2; 
    }); 
}); 
for (var item of result) { 
    console.log(item); 
} 

, основанный на https://curiosity-driven.org/monads-in-javascript#list

Смежные вопросы