さて、あなた自身の質問に答えるのは本当に良い形ではありませんが、それが他の誰かを啓発する場合に備えて、私の考えを書き留めておきます。(疑わしい...)
モナドが「コンテナ」と考えることができる場合、両方ともかなり明白なセマンティクスreturn
を持っています。1要素のコンテナーを生成し、コンテナーのコンテナーを単一のコンテナーに変換します。それについては何も難しいことはありません。join
return
join
それでは、より自然に「アクション」と考えられるモナドに焦点を当てましょう。その場合、は、 「実行」したときm x
にタイプの値を生成するある種のアクションです。特別なことは何もせず、次にを生成します。を生成するアクションを実行し、それを計算して適用するアクションを作成し、結果を返します。ここまでは順調ですね。x
return x
x
fmap f
x
x
f
f
それ自体がアクションを生成する場合、最終的には。になることはかなり明白ですm (m x)
。つまり、別のアクションを計算するアクションです。>>=
ある意味では、アクションを実行する関数や「アクションを生成する関数」などよりも、頭を包み込む方が簡単かもしれません。
したがって、論理的に言えば、join
最初のアクションを実行し、それが生成するアクションを実行してから、それを実行するようです。(むしろ、join
ヘアを分割したい場合は、今説明したことを実行するアクションを返します。)
それが中心的な考え方のようです。を実装するjoin
には、アクションを実行し、次に別のアクションを実行してから、それを実行します。(この特定のモナドにとって「実行」が意味するものは何でも。)
この洞察を考えると、私はいくつかのjoin
実装を書くことに挑戦することができます:
join Nothing = Nothing
join (Just mx) = mx
外側のアクションがの場合はNothing
、を返しNothing
、そうでない場合は内側のアクションを返します。繰り返しになりMaybe
ますが、これはアクションというよりはコンテナのようなものなので、別のことを試してみましょう...
newtype Reader s x = Reader (s -> x)
join (Reader f) = Reader (\ s -> let Reader g = f s in g s)
それは...無痛でした。AReader
は実際には、グローバル状態を取り、その結果を返すだけの関数です。したがって、スタックを解除するには、グローバル状態を外部アクションに適用します。これにより、新しいが返されますReader
。次に、この内部関数にも状態を適用します。
ある意味では、通常の方法よりもおそらく簡単です。
Reader f >>= g = Reader (\ s -> let x = f s in g x)
さて、どちらがリーダー関数で、どちらが次のリーダーを計算する関数ですか...?
State
それでは、古き良きモナドを試してみましょう。ここでは、すべての関数が入力として初期状態を取りますが、出力とともに新しい状態も返します。
data State s x = State (s -> (s, x))
join (State f) = State (\ s0 -> let (s1, State g) = f s0 in g s1)
それはそれほど難しくありませんでした。基本的には実行してから実行します。
今からタイピングをやめます。私の例では、すべての不具合とタイプミスを自由に指摘してください...:-/