Help us understand the problem. What is going on with this article?

Pythonでもジェネレーターで関数モナドとStateモナドを模倣してみた

ジェネレーターを DSL のように使って関数モナドと State モナドを模倣してみました。記述をそれっぽく見せることに重点を置いたため、bind や return を正確に実装したわけではありません。

この記事は次の記事の Python 版です。同じことが出来るはずなので確認したくなりました。

結果的に、ジェネレーターの return の仕様の変遷や、デコレーターが有用なことなどが分かりました。

実装

実装を並べると、関数モナドと State モナドの差分が分かりやすいです。

関数モナド State モナド
def function_monad(g):
  def f(state):
    it = g()
    value = None
    try:
      while True:
        value = it.send(value)(state)
    except StopIteration as e:
      return e.value
  return f
def state_monad(g):
  def f(state):
    it = g()
    value = None
    try:
      while True:
        value, state = it.send(value)(state)
    except StopIteration as e:
      return (e.value, state)
  return f

関数モナドでは state は固定で value のみが更新されますが、State モナドでは両者をセットで扱い更新されます。

これを踏まえてState モナドで使う getput を見れば、挙動が分かりやすいと思います。

get = lambda state: (state, state)
put = lambda newState: lambda oldState: (None, newState)

サンプル

以前書いた次の記事から引用しました。

関数モナド

test に対する引数が、yield の右のラムダ式に引数として与えられます。

Haskell Python JavaScript

test = do
  a <- (+ 1)
  b <- (* 2)
  return (a, b)

main = do
  print (test 3)
  print (test 5)
実行結果
(4,6)
(6,10)
@function_monad
def test():
  a = yield lambda x: x + 1
  b = yield lambda x: x * 2
  return (a, b)


print(test(3))
print(test(5))
実行結果
(4, 6)
(6, 10)
let test = functionMonad(
  function*() {
    let a = yield x => x + 1;
    let b = yield x => x * 2;
    return [a, b];
  });

log(test(3));
log(test(5));
実行結果
[4,6]
[6,10]

オンラインで実行 (Repl.it)

State モナド

State モナドはコンテキストとして状態を持っており、呼び出す際に初期値を与えます。状態の取得は get、更新は put で行います。

Haskell Python JavaScript

test = do
  a <- get
  put (a * 2)
  b <- get
  return (a, b)


postInc = do
  x <- get
  put (x + 1)
  return x


test2 = do
  a <- postInc
  b <- postInc
  return (a, b)

main = do
  print (evalState test  3)
  print (evalState test  5)
  print (evalState test2 3)
  print (evalState test2 5)
実行結果
(3,6)
(5,10)
(3,4)
(5,6)
@state_monad
def test():
  a = yield get
  yield put(a * 2)
  b = yield get
  return (a, b)

@state_monad
def postInc():
  x = yield get
  yield put(x + 1)
  return x

@state_monad
def test2():
  a = yield postInc
  b = yield postInc
  return (a, b)


print(test (3)[0])
print(test (5)[0])
print(test2(3)[0])
print(test2(5)[0])
実行結果
(3, 6)
(5, 10)
(3, 4)
(5, 6)
let test = stateMonad(
  function*() {
    let a = yield get;
    yield put(a * 2);
    let b = yield get;
    return [a, b];
  });
let postInc = stateMonad(
  function*() {
    let x = yield get;
    yield put(x + 1);
    return x;
  });
let test2 = stateMonad(
  function*() {
    let a = yield postInc;
    let b = yield postInc;
    return [a, b];
  });

log(test (3)[0]);
log(test (5)[0]);
log(test2(3)[0]);
log(test2(5)[0]);
実行結果
[3,6]
[5,10]
[3,4]
[5,6]

オンラインで実行 (Repl.it)

リストモナド

同じ方式でリストモナドを実装できないか考えたのですが、多重ループとなる場合に変数の値を変えて同じコードを何度も実行する必要があり、実現する方法が思いつかずに断念しました。

ジェネレーターの中で普通に for で多重ループを書けば同じことはできます。

Haskell Python JavaScript




test = do
  x <- [1, 2]
  y <- [3, 4]
  [x, y]



main =
  print test
実行結果
[1,3,1,4,2,3,2,4]
def list_monad(g):
  return list(g())

@list_monad
def test():
  for x in [1, 2]:
    for y in [3, 4]:
      yield x; yield y




print(test)
実行結果
[1, 3, 1, 4, 2, 3, 2, 4]
function listMonad(g) {
  return Array.from(g());
}
let test = listMonad(
  function*() {
    for (let x of [1, 2]) {
      for (let y of [3, 4]) {
        yield x; yield y
      }
    }
  });

log(test);
実行結果
[1,3,1,4,2,3,2,4]

オンラインで実行 (Repl.it)

リスト内包表記で書くとフラット化が必要になります。

>>> [[x, y] for y in [3, 4] for x in [1, 2]]
[[1, 3], [2, 3], [1, 4], [2, 4]]
>>> sum([[x, y] for y in [3, 4] for x in [1, 2]], [])
[1, 3, 2, 3, 1, 4, 2, 4]

【参考】Python 3 で flatten する方法いろいろ

return

ジェネレーターで値付きの return が使えるようになったのは割と最近のことのようです。

これは組み込み例外 — Python 3.7.3 ドキュメント に記述があり、PEP 479 -- Change StopIteration handling inside generators がデフォルトで有効化されているためです。

以前のバージョンではエラーになっていたようです。

SyntaxError: 'return' with argument inside generator

デコレーター

今回はデコレーターが大活躍しています。少し前に次の記事で知りました。

Python用語集に、以下の記載があります。

decorator

(デコレータ) 別の関数を返す関数で、通常、 @wrapper 構文で関数変換として適用されます。デコレータの一般的な利用例は、 classmethod() と staticmethod() です。

デコレータの文法はシンタックスシュガーです。次の2つの関数定義は意味的に同じものです:

def f(...):
   ...
f = staticmethod(f)

@staticmethod
def f(...):
   ...

※ 併記した JavaScript では関数でジェネレーターを包む様子を直接表記しています。

参考

Haskell のモナドは次の記事を参考にしてください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした