Zenn
📑

リストモナドをPythonで再現したい part 2

2025/03/28に公開

前回のおさらい

https://zenn.dev/kazuma624/articles/cc1022c7407322

Python で Haskell のリストモナドを再現しました。

def foldr(f, z, xxs):
    if xxs == []:
        return z

    x = xxs[0]
    xs = xxs[1:]
    return f(x, foldr(f, z, xs))


def concat(xxs):
    plus = lambda x, y: x + y
    return foldr(plus, [], xxs)

def ret(a):
    """
    return :: a -> m a の代わり
    """
    return [a]


def bind(ma, a_to_mb):
    """
    (>>=) :: m a -> ( a -> m b) -> m b の代わり
    """
    return concat(list(map(a_to_mb, ma)))

前回は以下の一文で締めました。

次回はこれを満たすことを確認したいと思います。

今回はこれを確認したいと思います。

モナド則を満たすのか?

モナドは以下を満たしていないといけません。

  • return a >>= ff a は等価
  • m >>= returnm は等価
  • (m >>= f) >>= gm >>= (\x -> f x >>= g) は等価

一般的な証明をするわけにもいかないので、簡単な具体例で確認します。

とはいえ、まず Python で無理やり実装しているので、ちょっとした読み替えが必要です。

  • return aret(a) と読み替えられます。
  • ma >>= fbind(ma, f) と読み替えられます。

return a >>= ff a が等価

af を以下とします。

>>> a = 2
>>> f = lambda x: [x, x * 2]

return a >>= fbind(ret(a), f) で表せます。
すると、

>>> bind(ret(a), f)
[2, 4]
>>> f(a)
[2, 4]

合ってますね。

a を文字列にしても大丈夫なようです。

>>> a = "ABC"
>>> f = lambda x: [x, x * 2]
>>> bind(ret(a), f)
['ABC', 'ABCABC']
>>> f(a)
['ABC', 'ABCABC']

m >>= returnm が等価

同じく af を以下とします。

>>> a = 2
>>> f = lambda x: [x, x * 2]

m はただリストにぶち込むだけなので、こうなります。(ですよね???)

def m(a):
    return [a]

return と同じですが、多分合ってると思います。

ここで m は関数ですが、そのまま使うのは難しいので何かしらの a に適用した結果を使います。

>>> bind(m(a), ret)
[2]
>>> m(a)
[2]

大丈夫そうです。

文字列版も試します。

>>> a = "ABC"
>>> f = lambda x: [x, x * 2]
>>> bind(ret(a), f)
['ABC']
>>> m(a)
['ABC']

同じく大丈夫そうです。

(m >>= f) >>= gm >>= (\x -> f x >>= g) が等価

最後です。

>>> a = 2
>>> f = lambda x: [x, x * 2]
>>> g = lambda x: [x + 1, x + 5]

ちょっと式が複雑になります。

最初は(m >>= f) >>= g の表現です。
m >>= fbind(m, f) ですが、これも具体的な値 a を入れることにします。
すると bind(m(a), f) となりますから、これを bind(XXXX, g) の XXXX の場所に入れて bind(bind(m(a), f), g) が得られます。

もう一方 m >>= (\x -> f x >>= g) は、 bind(m(a), YYYY) の YYYY の場所に (\x -> f x >>= g) つまり lambda x: bind((f), g) を代入すれば良いので bind(m(a), lambda x: bind(f(x), g)) です。

実際に計算させると

>>> bind(bind(m(a), f), g)
[3, 7, 5, 9]
>>> bind(m(a), lambda x: bind(f(x), g))
[3, 7, 5, 9]

一致します。

文字列バージョンも試します。

>>> a = "ABC"
>>> f = lambda x: [x, x * 2]
>>> g = lambda x: [x + "p", x + "q"]
>>> bind(bind(m(a), f), g)
['ABCp', 'ABCq', 'ABCABCp', 'ABCABCq']
>>> bind(m(a), lambda x: bind(f(x), g))
['ABCp', 'ABCq', 'ABCABCp', 'ABCABCq']

一致します。

次は do 記法の再現か?

やりたい・・・!

Discussion

ログインするとコメントできます