diff --git a/functional-programming-lean/src/SUMMARY.md b/functional-programming-lean/src/SUMMARY.md index 712b270..f3b9ec9 100644 --- a/functional-programming-lean/src/SUMMARY.md +++ b/functional-programming-lean/src/SUMMARY.md @@ -43,7 +43,7 @@ - [アプリカティブ関手の約定](functor-applicative-monad/applicative-contract.md) - [オルタナティブ](functor-applicative-monad/alternative.md) - [宇宙](functor-applicative-monad/universes.md) - - [The Complete Definitions](functor-applicative-monad/complete.md) + - [完全な定義](functor-applicative-monad/complete.md) - [Summary](functor-applicative-monad/summary.md) - [Monad Transformers](monad-transformers.md) - [Combining IO and Reader](monad-transformers/reader-io.md) diff --git a/functional-programming-lean/src/functor-applicative-monad/complete.md b/functional-programming-lean/src/functor-applicative-monad/complete.md index 51012f7..5f11492 100644 --- a/functional-programming-lean/src/functor-applicative-monad/complete.md +++ b/functional-programming-lean/src/functor-applicative-monad/complete.md @@ -1,132 +1,261 @@ + +# 完全な定義 + + + +ようやく関連する言語機能をすべて紹介したので、本節ではLeanの標準ライブラリにある `Functor` と `Applicative` 、`Monad` の正真正銘完全な定義について説明します。理解促進のために細部に至るまで省略しません。 + + +## 関手 + + +`Functor` クラスの完全な定義では、宇宙多相とデフォルトメソッドの実装が用いられています: + ```lean {{#example_decl Examples/FunctorApplicativeMonad/ActualDefs.lean HonestFunctor}} ``` + + +この定義において、`Function.comp` は関数合成のことで、よく `∘` という演算子として記述されます。`Function.const` は **定数関数** (constant function)で、引数を2つ取り、2つ目を無視する関数です。この関数に1つだけ引数を適用することで常に同じ値を返す関数が出来上がります。これは関数を要求するAPIに対して、プログラムとしては引数ごとに異なる計算結果を返す必要がない場合に有用です。`Function.const` の定義を簡潔にすると以下のようになります: + ```lean {{#example_decl Examples/FunctorApplicativeMonad/ActualDefs.lean simpleConst}} ``` + + +この関数に1つ引数を適用した上で `List.map` の関数の引数として使うとその便利さがよくわかるでしょう: + ```lean {{#example_in Examples/FunctorApplicativeMonad/ActualDefs.lean mapConst}} ``` ```output info {{#example_out Examples/FunctorApplicativeMonad/ActualDefs.lean mapConst}} ``` + + +この関数は実際には以下のシグネチャを持ちます: + ```output info {{#example_out Examples/FunctorApplicativeMonad/ActualDefs.lean FunctionConstType}} ``` + +ここでは型引数 `β` は明示的な引数であるため、`Functor.mapConst` のデフォルトの定義ではこの引数に `_` を与えることでプログラムの型チェックが通るような `Function.const` に渡される一意な型を見つけるようにLeanに指示します。`{{#example_in Examples/FunctorApplicativeMonad/ActualDefs.lean unfoldCompConst}}` は `{{#example_out Examples/FunctorApplicativeMonad/ActualDefs.lean unfoldCompConst}}` と等価です。 + + + +`Functor` クラスは `u+1` と `v` のうち大きい方の宇宙に存在します。ここで、 `u` は `f` の引数として受け入れられる宇宙レベルで、`v` は `f` が返す宇宙です。`Functor` 型クラスを実装する構造体が何故 `u` よりも大きな宇宙に存在しなければならないかということを説明するために、まず簡略化したクラス定義から始めます: + ```lean {{#example_decl Examples/FunctorApplicativeMonad/ActualDefs.lean FunctorSimplified}} ``` + + +この型クラスの構造体型は以下の帰納型と等価です: + ```lean {{#example_decl Examples/FunctorApplicativeMonad/ActualDefs.lean FunctorDatatype}} ``` + +`Functor.mk` の引数として渡される `map` メソッドの実装には `Type u` 上の2つの型を引数に取る関数を含みます。これは関数自体の型が `Type (u+1)` 上にあることを意味するため、`Functor` も少なくとも `u+1` のレベルでなければなりません。同様に、関数の他の引数は `f` を適用して作られた型を持つことから、`Functor` も少なくとも `v` のレベルでなければなりません。本節のすべての型クラスはこの性質を共有しています。 + + + +## アプリカティブ関手 + + +`Applicative` 型クラスは実際にはいくつかの小さなクラスから構成されており、それぞれに関連するメソッドが含まれています。最初は `Pure` と `Seq` で、それぞれ `pure` と `seq` を含んでいます: + ```lean {{#example_decl Examples/FunctorApplicativeMonad/ActualDefs.lean Pure}} {{#example_decl Examples/FunctorApplicativeMonad/ActualDefs.lean Seq}} ``` + + +これらに加えて、`Applicative` は `SeqRight` とこれに似た `SeqLeft` クラスにも依存しています: + ```lean {{#example_decl Examples/FunctorApplicativeMonad/ActualDefs.lean SeqRight}} {{#example_decl Examples/FunctorApplicativeMonad/ActualDefs.lean SeqLeft}} ``` + + +[オルタナティブとバリデーションについての節](alternative.md) で紹介した `seqRight` 関数は作用の観点から理解するのが最も簡単です。`{{#example_in Examples/FunctorApplicativeMonad.lean seqRightSugar}}` は脱糖すると `{{#example_out Examples/FunctorApplicativeMonad.lean seqRightSugar}}` となります。これは最初に `E1` 、次に `E2` を実行し、`E2` の結果のみを返却するものとして理解できます。`E1` から発生する作用によって `E2` が実行されなかったり、複数回実行されることがあり得ます。実際、`f` が `Monad` インスタンスを持つ場合、`E1 *> E2` は `do let _ ← E1; E2` と等価ですが、`seqRight` は `Validate` のようなモナドではない型でも使用できます。 + + +これのいとこである `seqLeft` は一番左の式の値を返すという点を除けば非常に似ています。`{{#example_in Examples/FunctorApplicativeMonad/ActualDefs.lean seqLeftSugar}}` は `{{#example_out Examples/FunctorApplicativeMonad/ActualDefs.lean seqLeftSugar}}` に脱糖されます。`{{#example_in Examples/FunctorApplicativeMonad/ActualDefs.lean seqLeftType}}` は `{{#example_out Examples/FunctorApplicativeMonad/ActualDefs.lean seqLeftType}}` 型を持ち、これが `f α` を返すことを除けば `seqRight` とそっくりです。`{{#example_in Examples/FunctorApplicativeMonad/ActualDefs.lean seqLeftSugar}}` は最初に `E1` を、次に `E2` を実行し、最初の `E1` の結果を返すプログラムだと理解できます。もし `f` が `Monad` インスタンスを持つ場合、`E1 <* E2` は `do let x ← E1; _ ← E2; pure x` と等価です。一般的に、`seqLeft` はバリデーションやパーサのような処理にて、値そのものを変化させずに値に対する追加の条件を指定するのに便利です。 + + +`Applicative` の定義は `Functor` に加えてこれらすべてのクラスを継承しています: + ```lean {{#example_decl Examples/FunctorApplicativeMonad/ActualDefs.lean Applicative}} ``` + +`Applicative` の完全な定義には `pure` と `seq` の定義だけが必要です。というのも `Functor` ・`SeqLeft` ・`SeqRight` のすべてのメソッドにデフォルトの定義があるからです。`Functor` の `mapConst` メソッドには `Functor.map` によるデフォルトの実装があります。これらのデフォルト実装をオーバーライドして良いのは、挙動が同じなのにより効率的な新しい関数がある場合のみとすべきです。デフォルト実装はその実装について、自動生成コードと同じ正しさを持つことへの仕様とみなすべきでしょう。 + + + +`seqLeft` のデフォルト実装はとてもコンパクトです。いくつかの名前を糖衣構文や定義に置き換えると、別の視点が見えてきます。以下のデフォルト実装: + ```lean {{#example_in Examples/FunctorApplicativeMonad/ActualDefs.lean unfoldMapConstSeqLeft}} ``` + + +は以下になります: + ```lean {{#example_out Examples/FunctorApplicativeMonad/ActualDefs.lean unfoldMapConstSeqLeft}} ``` + +`(fun x _ => x) <$> a` はどう解釈したらよいでしょうか?ここで、`a` は `f α` 型で、`f` は関手です。もし `f` が `List` の場合、`{{#example_in Examples/FunctorApplicativeMonad/ActualDefs.lean mapConstList}}` は `{{#example_out Examples/FunctorApplicativeMonad/ActualDefs.lean mapConstList}}` に評価されます。もし `f` が `Option` の場合、`{{#example_in Examples/FunctorApplicativeMonad/ActualDefs.lean mapConstOption}}` は `{{#example_out Examples/FunctorApplicativeMonad/ActualDefs.lean mapConstOption}}` に評価されます。どちらの場合も、関手の中の値は引数を無視してもとの値を返す関数に置き換えられます。これを `seq` と組み合わせると、この関数は `seq` の第2引数を破棄します。 + + + +`seqRight` のデフォルト実装は、`const` に追加の引数 `id` があることを除けば `seqLeft` に非常によく似ています。この定義も、最初に標準的な糖衣構文を導入し、次にいくつかの名前をその定義に置き換えることで同じように理解できます: + ```lean {{#example_eval Examples/FunctorApplicativeMonad/ActualDefs.lean unfoldMapConstSeqRight}} ``` + +`(fun _ x => x) <$> a` はどう解釈したらよいでしょうか?ここでも例が役に立ちます。`{{#example_in Examples/FunctorApplicativeMonad/ActualDefs.lean mapConstIdList}}` は `{{#example_out Examples/FunctorApplicativeMonad/ActualDefs.lean mapConstIdList}}` に評価され、`{{#example_in Examples/FunctorApplicativeMonad/ActualDefs.lean mapConstIdOption}}` は `{{#example_out Examples/FunctorApplicativeMonad/ActualDefs.lean mapConstIdOption}}` に評価されます。言い換えると、`(fun _ x => x) <$> a` は `a` の全体的な形を保ちつつ、しかし各値を恒等関数に置き換えます。作用の観点からは、`a` の副作用は発生しますが、`seq` と一緒に使われることで、値が捨てられます。 + + + +## モナド + + +`Applicative` を構成する操作がそれぞれの型クラスに分かれているように、`Bind` も独自のクラスを持ちます: + ```lean {{#example_decl Examples/FunctorApplicativeMonad/ActualDefs.lean Bind}} ``` + + +`Monad` は `Bind` と共に `Applicative` を継承しています: + ```lean {{#example_decl Examples/FunctorApplicativeMonad/ActualDefs.lean Monad}} ``` + +クラスの階層全体から継承したメソッドとデフォルトのメソッドを走査すると、`Monad` インスタンスに必要な実装は `bind` と `pure` だけであることがわかります。つまり、`Monad` インスタンスは自動的に `seq` ・`seqLeft` ・`seqRight` ・`map` ・`mapConst` の実装を生成します。APIの教会から見ると、`Monad` インスタンスを持つ型は `Bind` ・`Pure` ・`Seq` ・`Functor` ・`SeqLeft` ・`SeqRight` のインスタンスを持つことになります。 + + +## 演習問題 - 1. Understand the default implementations of `map`, `seq`, `seqLeft`, and `seqRight` in `Monad` by working through examples such as `Option` and `Except`. In other words, substitute their definitions for `bind` and `pure` into the default definitions, and simplify them to recover the versions `map`, `seq`, `seqLeft`, and `seqRight` that would be written by hand. - 2. On paper or in a text file, prove to yourself that the default implementations of `map` and `seq` satisfy the contracts for `Functor` and `Applicative`. In this argument, you're allowed to use the rules from the `Monad` contract as well as ordinary expression evaluation. + + 1. `Option` や `Except` などを例にして、`Monad` の `map` ・`seq` ・`seqLeft` ・`seqRight` のデフォルト実装を解釈してください。つまり、 `bind` と `pure` の定義をデフォルトの定義に置き換えて、`map` ・`seq` ・`seqLeft` ・`seqRight` を手で書けるように単純化してください。 + 2. `map` と `seq` のデフォルト実装が `Functor` と `Applicative` の約定を満たすことを手書き、もしくはテキストファイルで証明してください。この際には、通常の式の評価と同様に `Monad` の約定のルールを使用しても構いません。