现在
node.js
支持
ECMAScript调和生成器
我们可以简洁地编写一元代码
Haskell中的块:
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);
}在上面的代码中
是一个函数,可用于创建
确定性
monads,如:
var maybe = monad(function (a) {
return {just: a};
}, function (m, f) {
return m === null ? null : f(m.just);
});您现在可以使用
如下所示:
var readZip = maybe(function * (a, b) {
var a = yield readList(a);
var b = yield readList(b);
return _.zip(a, b);
});上述函数
获取两个字符串,将它们转换为列表,然后压缩它们。如果出现错误,它会立即返回
..。它取决于以下函数:
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类似地,我们可以创建任何其他确定性单子。例如,我最喜欢的
monad:
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);
});
};
});现在我们可以使用
要简洁地创建连续传递样式的函数,请执行以下操作:
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(10)(function (a) { console.log(a); }); // 55不幸的是
仅适用于确定性单体。它不适用于像
monad,因为您只能从特定位置恢复生成器一次。
所以我的问题是:有没有其他方法来实现像
monad在JavaScript中简明扼要?
发布于 2019-06-29 13:42:05
所以我的问题是:有没有其他方法来实现像
monad在JavaScript中简明扼要?
是的,您可以使用生成器在JavaScript中简洁地实现像list monad这样的非确定性monad。
免疫原
..。但是,由于JavaScript中的生成器不能多次从特定位置恢复,因此您必须通过创建和重放多个生成器来模拟此行为。这种解决方案有几个缺点。
它的效率很低,因为需要创建和重放多个生成器,导致时间复杂度的二次增长。
它只适用于纯monads和纯计算,因为需要创建和重放多个生成器。因此,副作用将被错误地多次执行。
我们需要不可变的生成器来创建不确定的monad,比如list monad。不可变生成器可以多次从特定位置恢复。不幸的是,JavaScript本身并不支持不可变的生成器。但是,我们可以通过创建和重放多个可变生成器来模拟它。因此,让我们看看如何创建一个不可变的生成器。
我们需要解决的第一个问题是将可变生成器重放到特定点。我们使用一种称为再生器的特殊函数类来实现这一点。再生器是任何返回可变生成器的函数。这类函数的最简单示例是
..。因此,每个生成器函数都是再生器,但并不是每个再生器都是生成器函数。您可以使用以下函数通过推进旧的再生器来创建新的再生器。
// type Regenerator = Arguments -> MutableGenerator
// next :: (Regenerator, Arguments) -> Regenerator
const next = (regen, ...args) => data => {
const gen = regen(...args);
return gen.next(data), gen;
};The The The
函数可用于将再生器推进到特定点。例如,考虑下面的代码片段。
const next = (regen, ...args) => data => {
const gen = regen(...args);
return gen.next(data), gen;
};
const regen1 = next(regen0, 1, 2, 3);
const regen2 = next(regen1, undefined); // first value of mutable generator ignored
const regen3 = next(regen2, 10);
const gen1 = regen3(20);
const gen2 = regen3(30);
const result1 = gen1.next(40).value; // 10 + 20 + 40
const result2 = gen2.next(50).value; // 10 + 30 + 50
console.log(result1, result2);
function* regen0(a, b, c) {
const x = yield a;
const y = yield b;
const z = yield c;
return x + y + z;
}
如您所见,我们可以使用
函数或将重新生成器应用于某个值以获得可变生成器。现在我们有能力将可变生成器重放到特定点,我们可以创建不可变生成器,如下所示。
// immutagen :: Regenerator -> Arguments -> ImmutableGenerator
const immutagen = regen => (...args) => function loop(regen) {
return (gen, data) => {
const {value, done} = gen.next(data);
if (done) return {value, next: null};
let replay = false;
const recur = loop(next(regen, data));
return {value, next: value => {
if (replay) return recur(regen(data), value);
replay = true; return recur(gen, value);
}};
};
}(next(regen, ...args))(regen(...args));The The The
函数可以用来创建不可变的生成器函数,我们可以调用它来生成不可变的生成器。下面是一个关于如何创建和使用不可变生成器的示例。
const next = (regen, ...args) => data => {
const gen = regen(...args);
return gen.next(data), gen;
};
const immutagen = regen => (...args) => function loop(regen) {
return (gen, data) => {
const {value, done} = gen.next(data);
if (done) return {value, next: null};
let replay = false;
const recur = loop(next(regen, data));
return {value, next: value => {
if (replay) return recur(regen(data), value);
replay = true; return recur(gen, value);
}};
};
}(next(regen, ...args))(regen(...args));
const foo = immutagen(function* (a, b, c) {
const x = yield a;
const y = yield b;
const z = yield c;
return x + y + z;
});
const bar = foo(1, 2, 3).next(10).next(20);
const result1 = bar.next(30).value; // 10 + 20 + 30
const result2 = bar.next(40).value; // 10 + 20 + 40
console.log(result1, result2);
最后,现在我们有了不可变的生成器,我们可以更简洁地实现像list monad这样的非确定性monad,如下所示:
const next = (regen, ...args) => data => {
const gen = regen(...args);
return gen.next(data), gen;
};
const immutagen = regen => (...args) => function loop(regen) {
return (gen, data) => {
const {value, done} = gen.next(data);
if (done) return {value, next: null};
let replay = false;
const recur = loop(next(regen, data));
return {value, next: value => {
if (replay) return recur(regen(data), value);
replay = true; return recur(gen, value);
}};
};
}(next(regen, ...args))(regen(...args));
const monad = bind => regen => (...args) => function loop({value, next}) {
return next ? bind(value, val => loop(next(val))) : value;
}(immutagen(regen)(...args));
const flatMap = (array, callback) => array.flatMap(callback);
const list = monad(flatMap);
const foo = list(function* (xs, ys) {
const x = yield xs;
const y = yield ys;
return [x * y];
});
console.log(foo([1, 2, 3], [4, 5, 6]));
请注意,
函数只需要
..。它不需要
..。
发布于 2013-12-22 22:30:54
所以我的问题是:有没有其他方法可以在JavaScript中简洁地实现像list monad这样的非确定性monad?
我建议这个monad实现,我将其应用于各种monad。
这里
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 [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]显然,"do“语法会导致回调地狱,但在
LiveScript
你可以减轻痛苦:
result = do
x <- List.unit 1 2 .bind
y <- @unit 3 4 .bind
@unit x + y你也可以给你的
创造性的方法:
result = do
x <- List.unit 1 2 .\>=
y <- @unit 3 4 .\>=
@unit x + y发布于 2015-08-10 17:35:09
似乎有一种很好的方式来实现像这样的list monad:
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
https://stackoverflow.com/questions/20729050
复制相似问题