17

node.jsがECMAScript Harmony ジェネレーターをサポートするようになったので、Haskell で単項コードを簡潔に alaブロックを記述できます。do

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は 2 つの文字列を取り、それらをリストに変換してから圧縮します。エラーが発生した場合は、すぐに を返します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 で簡潔に実装する方法は他にありますか?

4

5 に答える 5

3

私の質問はこれです: モナドのような非決定論的モナドlistを JavaScript で簡潔に実装する方法は他にありますか?

はい、リスト モナドのような非決定論的モナドを、immutagenのようにジェネレーターを使用して JavaScript で簡潔に実装できます。ただし、JavaScript のジェネレーターは特定の位置から複数回再開できないため、複数のジェネレーターを作成して再生することにより、この動作をエミュレートする必要があります。このソリューションには、いくつかの欠点があります。

  1. 複数のジェネレーターを作成して再生する必要があり、時間の複雑さが二次的に増加するため、非効率的です。
  2. 複数のジェネレーターを作成して再生する必要があるため、純粋なモナドと純粋な計算に対してのみ機能します。したがって、副作用が誤って複数回実行されます。

リストモナドなどの非決定論的なモナドを作成するために必要なものは、不変のジェネレーターです。不変ジェネレーターは、特定の位置から複数回再開できます。残念ながら、JavaScript は不変のジェネレーターをネイティブにサポートしていません。ただし、複数の可変ジェネレーターを作成して再生することでエミュレートできます。それでは、不変のジェネレーターを作成する方法を見てみましょう。

最初に解決しなければならない問題は、可変ジェネレーターを特定のポイントまで再生する方法です。これは、リジェネレータと呼ばれる特別なクラスの関数を使用して行います。リジェネレータは、変更可能なジェネレータを返す任意の関数です。このような関数の最も単純な例はfunction* () {}. したがって、すべてのジェネレーター関数は再生成器ですが、すべての再生成器がジェネレーター関数であるとは限りません。次の関数を使用して古い再生器を進めることにより、新しい再生器を作成できます。

// type Regenerator = Arguments -> MutableGenerator

// next :: (Regenerator, Arguments) -> Regenerator
const next = (regen, ...args) => data => {
    const gen = regen(...args);
    return gen.next(data), gen;
};

このnext機能を使用して、再生器を特定のポイントに進めることができます。たとえば、次のコード スニペットを考えてみましょう。

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

ご覧のとおり、next関数を使用して再生成器を進めるか、再生成器を値に適用して、変更可能な生成器を取得できます。可変ジェネレーターを特定のポイントまで再生できるようになったので、次のように不変ジェネレーターを作成できます。

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

このimmutagen関数を使用して、不変のジェネレーター関数を作成できます。これを呼び出して、不変のジェネレーターを生成できます。以下は、不変ジェネレーターを作成して使用する方法の例です。

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

最後に、イミュータブル ジェネレーターができたので、次のようにリスト モナドのような非決定論的モナドをより簡潔に実装できます。

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

monadこの関数は のみを必要とすることに注意してくださいbind。必要ありませんunit

于 2019-06-29T05:42:04.777 に答える
1

一般に、JS ではネストされた計算構造から抽象化することはできません。そのためには、エフェクト レイヤーを妥協したり、前の値に応じて次のエフェクトを決定するモナドの能力を失ったりする必要があります。

しかし、少なくともchainapplicatives のようなモナドを適用することで from を抽象化できます:

const arrChain = mx => fm =>
  mx.reduce((acc, x) => arrAppend(acc) (fm(x)), []);

const arrAppend = xs => ys =>
  (xs.push.apply(xs, ys), xs);

const chain2 = chain => tx => ty => fm =>
  chain(chain(tx) (x => fm(x)))
    (gm => chain(ty) (y => gm(y)));

const main = chain2(arrChain)
  ([1,2])
    ([3,4])
      (x => [y => [x, y]]); // nested constructor application

// prev-val-next-eff-dependency:

const main2 = chain2(arrChain)
  ([1,2])
    ([3,4])
        (x =>
          x === 1
            ? []
            : [y => [x, y]]);

console.log(main);
console.log(main2);

これは、次のアクションをアンラップするだけでなく、各効果が 1 回実行されるため、元の計算よりもわずかに効率が低下します。


これは、モナドと継続渡しスタイルを組み合わせた別のアプローチです。ただし、do 記法に代わるものではありません。

const chainv = ({chain}) => {
  const go = (mx, ...ms) => fm =>
    ms.length === 0
      ? chain(mx) (fm)
      : chain(mx) (x => fm(x) (go(...ms)));

  return go;
};

const arrChain = xs => fm =>
  xs.flatMap(fm);

const main = chainv({chain: arrChain}) (
  [1,2],
  [3,4],
  [5,6])
    (x => k =>
      k(y => k =>
        k(z => [x, y, z])));

// [1, 3, 5, 1, 3, 6, 1, 4, 5, 1, 4, 6, 2, 3, 5, 2, 3, 6, 2, 4, 5, 2, 4, 6]

const main2 = chainv({chain: arrChain}) (
  [1,2],
  [3,4],
  [5,6])
    (x => k =>
      x === 1
        ? []
        : k(y => k =>
          k(z => [x, y, z])));

// [2, 3, 5, 2, 3, 6, 2, 4, 5, 2, 4, 6]

console.log("main:", main);
console.log("main2:", main2);

于 2020-07-09T19:04:07.110 に答える