diff --git a/functional-programming-lean/src/SUMMARY.md b/functional-programming-lean/src/SUMMARY.md index 6d20997..8f7f3fb 100644 --- a/functional-programming-lean/src/SUMMARY.md +++ b/functional-programming-lean/src/SUMMARY.md @@ -39,7 +39,7 @@ - [まとめ](monads/summary.md) - [関手・アプリカティブ関手・モナド](functor-applicative-monad.md) - [構造体と継承](functor-applicative-monad/inheritance.md) - - [Applicative Functors](functor-applicative-monad/applicative.md) + - [アプリカティブ関手](functor-applicative-monad/applicative.md) - [The Applicative Contract](functor-applicative-monad/applicative-contract.md) - [Alternatives](functor-applicative-monad/alternative.md) - [Universes](functor-applicative-monad/universes.md) diff --git a/functional-programming-lean/src/functor-applicative-monad/applicative.md b/functional-programming-lean/src/functor-applicative-monad/applicative.md index c737aaf..c6e5711 100644 --- a/functional-programming-lean/src/functor-applicative-monad/applicative.md +++ b/functional-programming-lean/src/functor-applicative-monad/applicative.md @@ -1,240 +1,481 @@ + +# アプリカティブ関手 + + + +**アプリカティブ関手** (applicative functor)とは関手に `pure` と `seq` の2つの追加の操作を持ったもののことです。`pure` は `Monad` でも同じ演算子が用いられています。それもそのはずで、実は `Monad` は `Applicative` を継承しているからです。`seq` は `map` とよく似た演算子です:これによって、ある関数をデータ型の中身を変換するために使用することができます。しかし、`seq` では、関数自体がデータ型に含まれています:`{{#example_out Examples/FunctorApplicativeMonad.lean seqType}}` 。`Functor.map` が無条件に関数適用するのに対して、関数を `f` 型の下に持つことで、`Applicative` インスタンスは関数の適用方法を制御することができます。2番目の引数には `Unit →` で始まる型を指定することで、関数が適用されない場合に `seq` の定義をショートカットできるようにしています。 + + +この短絡的なふるまいの真価は、`Applicative Option` のインスタンスで見ることができます: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean ApplicativeOption}} ``` + + +このケースにおいて、`seq` に適用する関数が無ければ、引数を計算する必要もないので `x` が呼ばれることはありません。同じことが `Except` の `Applicative` インスタンスにも当てはまります: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean ApplicativeExcept}} ``` + + +この短絡的な挙動は関数自体というよりも、関数を **取り囲む** `Option` や `Except` の構造にのみ依存します。 + + +モナドは文を順次実行するという概念を純粋関数型言語に取り込むための方法とみなすことができます。ある文の結果はそれ以降の文の実行に影響を及ぼすことができます。これは `bind` の型:`{{#example_out Examples/FunctorApplicativeMonad.lean bindType}}` に見ることができます。最初の文の結果の値は、次に実行する文を計算する関数への入力となります。`bind` の連続した使用は命令型プログラミング言語における文の列のようなものです。`bind` は強力であり、条件分岐やループのような制御構造を実装することも十分可能です。 + +このアナロジーに従うと、`Applicative` は副作用を持つ言語での関数適用を捉えたものと言えます。KotlinやC#のような言語では、関数の引数は左から右に評価されます。先に評価された引数によって実行される副作用は、その後の引数によって実行される副作用よりも先に発生します。しかし、関数は引数の特定の **値** に依存するカスタムの短絡演算子を実装するほど強力ではありません。 + + +通常、`seq` は直接呼び出されません。その代わりに `<*>` という演算子が使われます。この演算子は第二引数を `fun () => ...` で包み、`seq` の呼び出しを単純化します。つまり、`{{#example_in Examples/FunctorApplicativeMonad.lean seqSugar}}` は `{{#example_out Examples/FunctorApplicativeMonad.lean seqSugar}}` の構文糖衣です。 + + +`seq` を複数の引数で使えるようにするにあたって、Leanでの複数の引数を持つ関数が、実際には「残りの引数を待つ別の関数」を返す「単一の引数を持つ関数」であるということは重要な特徴です。言い換えると、`seq` の最初の引数が複数の引数を持っている場合、 `seq` の結果は2個目以降の残りの引数を待っていることになります。例えば、`{{#example_in Examples/FunctorApplicativeMonad.lean somePlus}}` は `{{#example_out Examples/FunctorApplicativeMonad.lean somePlus}}` 型を持ちます。ここで引数を一つ与えると、`{{#example_in Examples/FunctorApplicativeMonad.lean somePlusFour}}` となり、型は `{{#example_out Examples/FunctorApplicativeMonad.lean somePlusFour}}` となります。これ自身も `seq` と一緒に使えるため、`{{#example_in Examples/FunctorApplicativeMonad.lean somePlusFourSeven}}` は `{{#example_out Examples/FunctorApplicativeMonad.lean somePlusFourSeven}}` 型を持ちます。 + + +すべての関手がアプリカティブにはなりません。`Pair` は組み込みの直積型 `Prod` のようなものです: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean Pair}} ``` + + +`Except` と同じように、`{{#example_in Examples/FunctorApplicativeMonad.lean PairType}}` は `{{#example_out Examples/FunctorApplicativeMonad.lean PairType}}` 型を持ちます。これは `Pair α` が `Type → Type` 型を持ち、`Functor` インスタンスの定義が可能であることを意味します: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean FunctorPair}} ``` + + +これは `Functor` の約定に従います。 + + +チェックする2つの特性は `{{#example_eval Examples/FunctorApplicativeMonad.lean checkPairMapId 0}} = {{#example_eval Examples/FunctorApplicativeMonad.lean checkPairMapId 2}}` と `{{#example_eval Examples/FunctorApplicativeMonad.lean checkPairMapComp1 0}} = {{#example_eval Examples/FunctorApplicativeMonad.lean checkPairMapComp2 0}}` です。最初の特性は、左辺の評価を逐一評価し、それが右辺の形に評価されることが確認できればOKです: + ```lean {{#example_eval Examples/FunctorApplicativeMonad.lean checkPairMapId}} ``` + + +2つ目は、両辺を逐一評価し、同じ結果が得られることを確認します: + ```lean {{#example_eval Examples/FunctorApplicativeMonad.lean checkPairMapComp1}} {{#example_eval Examples/FunctorApplicativeMonad.lean checkPairMapComp2}} ``` + + +しかし `Applicative` インスタンスを定義しようとしてもうまくいきません。`pure` の定義が必要になります: + ```lean {{#example_in Examples/FunctorApplicativeMonad.lean Pairpure}} ``` ```output error {{#example_out Examples/FunctorApplicativeMonad.lean Pairpure}} ``` + + +スコープ内に `β` 型の値(つまり `x` )があり、アンダースコアからのエラーメッセージは、次のステップとしてコンストラクタ `Pair.mk` を使用することを示唆しています: + ```lean {{#example_in Examples/FunctorApplicativeMonad.lean Pairpure2}} ``` ```output error {{#example_out Examples/FunctorApplicativeMonad.lean Pairpure2}} ``` + +残念ながら、`α` 型を利用する余地はありません。なぜなら、`pure` は `Applicative (Pair α)` のインスタンスを定義するために、αとして **可能なすべての型** に対して機能する必要がありますが、これは不可能です。極端な話、呼び出し元は `α` を値を全く持たない `Empty` にすることもあるかもしれないのです。 + + + +## 非モナドなアプリカティブ関手 + +フォームへのユーザ入力のバリデーションにあたっては、いちいち1つずつエラーを報告するのではなく、まとめて一度にエラーを出すことが一般的に最善と考えられています。これによってユーザはフィールドごとにエラーをうんざりしながら修正することなく、コンピュータに受け入れられるために何が必要かを概観することができます。 + + + +理想的には、ユーザ入力のバリデーションは、それを行う関数の型に現れます。この関数はチェックが行われたことを体現するデータ型を返却すべきです。例えば、テキストボックスに数値が含まれているかどうかをチェックする関数は、実際の数値型を返すべきです。バリデーションのルーチンは、入力がバリデーションを通過しなかったことを例外を投げることで表現できます。しかし、例外には大きな欠点があります:最初のエラーでプログラムが終了してしまうため、エラーのリストを蓄積することができないのです。 + + +一方で、エラーのリストを蓄積し、リストが空でなければ失敗にする一般的な設計パターンにも問題があります。入力データの各部分をバリデーションする `if` 文の長くネストされた列はメンテナンス性に欠け、出てきたエラーメッセージから何個かのエラーをいともたやすく見落とすでしょう。理想的には、バリデーションは新しい値を返しつつエラーメッセージを自動的に追跡して蓄積するようなAPIを使って動作するものであってほしいでしょう。 + +`Validate` というアプリカティブ関手はまさにそんなAPIを実装する1つの方法を提供します。`Except` モナドのように、`Validate` によってバリデーションされたデータを正確に特徴づける新しい値を構築することができます。しかし一方で `Except` と異なり、リストが空かどうかをチェックし忘れるリスク無しに複数のエラーを蓄積することができます。 + + + +### ユーザ入力 + + + +ユーザ入力の例として、次のような構造を考えてみましょう: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean RawInput}} ``` + + +実装ビジネスロジックは以下の通りです: + + + 1. 名前は空であってはならない + 2. 誕生年は非負の数値でなければならない + 3. 誕生年は1900より大きく、かつバリデーションを行った年以下でなければならない + + +これらをデータ型として表現するには、**部分型** と呼ばれる新しい機能が必要になります。このツールがあれば、バリデーションのフレームワークをエラーを追跡するアプリカティブ関手を使って書くことができ、バリデーションルールもこのフレームワークで実装することができます。 + + + +### 部分型 + + + +これらの条件を表現するにあたって最も簡単なのは、`Subtype` と呼ばれるLeanの型を1つ追加することです: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean Subtype}} ``` + + +この構造体には2つの型パラメータがあります:1つはデータの型を表す暗黙パラメータ `α` で、もう1つは `α` に対する述語を表す明示的なパラメータ `p` です。**述語** (predicate)とは実際の文を生成するために値に置き換えることができる変数を持つ論理的な文であり、[`GetElem` のパラメータ](../type-classes/indexing.md#overloading-indexing) が検索に用いる添え字が範囲内であることの意味を記述するのはその一例です。`Subtype` の場合、述語は `α` の値の中で述語が成り立つ部分集合を切り出します。この構造体の2つのフィールドはそれぞれ `α` の値と、その値が述語 `p` を満たす根拠です。Leanは `Subtype` に対して特別な構文を持っています。`p` が `α → Prop` 型を持つ場合、`Subtype p` 型は `{{#example_out Examples/FunctorApplicativeMonad.lean subtypeSugar}}` と書くこともでき、型が自動的に推論される場合は `{{#example_out Examples/FunctorApplicativeMonad.lean subtypeSugar2}}` と書くことさえもできます。 + + +[正の整数を帰納的な型として表現すること](../type-classes/pos.md) は明快で、プログラムしやすいです。しかし、これには重要な欠点があります。`Nat` と `Int` はLeanプログラムにおいては普通の帰納型の構造を持っていますが、コンパイラはこれらを特別に扱い、高速な任意精度の数値ライブラリを使用して実装します。これは後から追加されるユーサ定義型には当てはまりません。しかし、`Nat` の部分型を0以外の数に制限することで、コンパイル時に0を除きながら効率的な表現を使用することができます: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean FastPos}} ``` + + +最も小さい正の整数はもちろん1です。さて、これは帰納型のコンストラクタではなく、角括弧で構成される構造体のインスタンスです。第1引数は基礎となる `Nat` で、第2引数はその `Nat` が0より大きいという根拠です: + ```leantac {{#example_decl Examples/FunctorApplicativeMonad.lean one}} ``` + + +`OfNat` のインスタンスは `Pos` のインスタンスと非常によく似ていますが、`n + 1 > 0` という根拠を提供するために短いタクティクによる証明を使う点が異なります: + ```leantac {{#example_decl Examples/FunctorApplicativeMonad.lean OfNatFastPos}} ``` + + +`simp_arith` タクティクは `simp` に算術的な等式を追加したバージョンです。 + +部分型は諸刃の剣です。これによってバリデーションルールの効率的な表現を可能にしますが、これらのルールを維持する負担をライブラリのユーザに移し、ユーザは重要な不変量に違反していないことを **証明** しなければなりません。一般的には、ライブラリの内部で使用し、すべての不変量が満たされていることを自動的に保障するAPIをユーザに提供し、必要な証明はライブラリの内部で行うのが良いでしょう。 + + + +ある `α` 型の値が `{x : α // p x}` の部分型に含まれるかどうかを調べるには、通常 `p x` という命題が決定可能である必要があります。[等式と順序クラスについての節](../type-classes/standard-classes.md#equality-and-ordering) では、決定可能な命題を `if` と一緒に使用する方法について説明しました。`if` を決定可能な命題で使用する場合、名前を指定することができます。`then` ブランチでは、その名前は命題が真であることの根拠に束縛され、`else` ブランチでは命題が偽であることの根拠に束縛されます。これは、与えられた `Nat` が正であるかどうかをチェックする時に便利です: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean NatFastPos}} ``` + +`then` ブランチでは、`h` は `n > 0` という根拠に束縛され、この根拠は `Subtype` のコンストラクタの第2引数として使うことができます。 + +### 入力のバリデーション + + + +バリデーションされたユーザ入力は、以下の技術を駆使してビジネスロジックを表現した構造体です: + + + * 構造体の型自体はバリデーションチェックが行われた年をエンコード。そのため `CheckedInput 2019` は `CheckedInput 2020` と同じ型ではありません。 + * 誕生年は `String` ではなく `Nat` で表現 + * 名前と誕生年のフィールドの許容される値を制約するために部分型を使用 + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean CheckedInput}} ``` + + +入力のバリデータは現在の年と `RawInput` を引数に取り、チェック済みの入力か、少なくとも1つのバリデーション失敗のどちらかを返すべきです。これは `Validate` 型で表されます: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean Validate}} ``` + + +見た目は `Except` によく似ています。唯一の違いは、`error` コンストラクタに複数の失敗を含めることができる点です。 + + +Validateは関手です。これに関数をマッピングすることで、`Except` の `Functor` インスタンスと同じように、成功した値の場合はその値が変換されます: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean FunctorValidate}} ``` + + +`Validate` の `Applicative` インスタンスには `Except` のインスタンスと重要な違いがあります:`Except` のインスタンスは最初に遭遇したエラーで終了するのに対して、`Validate` のインスタンスは関数と引数のブランチの **両方** からのすべてのエラーを蓄積することに注意を払っています: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean ApplicativeValidate}} ``` + + +`.errors` を `NonEmptyList` のコンストラクタと一緒に使うと少し冗長になります。`reportError` のような補助関数によって可読性が上がります。このアプリケーションでは、エラーのレポートはフィールド名とメッセージの組み合わせで構成されます: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean Field}} {{#example_decl Examples/FunctorApplicativeMonad.lean reportError}} ``` + + +`Validate` の `Applicative` インスタンスでは、各フィールドのチェック手順を個別に記述し、組み合わせることができます。名前のチェックでは、文字列が空でないことを確認し、その根拠を `Subtype` という形で返します。これは `if` の根拠による束縛のバージョンを使用しています: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean checkName}} ``` + +`then` ブランチでは、`h` は `name = ""` という根拠に束縛され、`else` ブランチでは `¬name = ""` という根拠に束縛されます。 + + + +バリデーションエラーによってはほかのチェックが不可能になってしまうケースも存在します。例えば、混乱したユーザが誕生年として数字の代わりに `"syzygy"` と書いた場合、誕生年のフィールドが1900年より大きいかどうかをチェックしても無意味です。数値の許容範囲をチェックすることは、そもそもフィールドに実際に数値が含まれていることを確認した後でのみ意味があります。これは関数 `andThen` を使って表現することができます: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean ValidateAndThen}} ``` + + +この関数の型シグネチャは `Monad` インスタンスの `bind` として使用するのに適していますが、そうしないのにはれっきとした理由があります。それについては [`Applicative` の約定を説明する節](applicative-contract.md#additional-stipulations) で説明します。 + + +誕生年が数字であることを確認するには、`String.toNat? : String → Option Nat` という組み込み関数が便利です。先に `String.trim` を使って先頭と末尾の空白を除去するととてもユーザフレンドリになります: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean checkYearIsNat}} ``` + + +提供された年が予想される範囲内であることをチェックするには、`if` の根拠提供の形式をネストして使用します: + ```leantac {{#example_decl Examples/FunctorApplicativeMonad.lean checkBirthYear}} ``` + + +最後に、これら3つの要素を `seq` を使って結合します: + ```lean {{#example_decl Examples/FunctorApplicativeMonad.lean checkInput}} ``` + + +`checkInput` をテストしてみると、実際に複数のフィードバックを返してくれることがわかるでしょう: + ```lean {{#example_in Examples/FunctorApplicativeMonad.lean checkDavid1984}} ``` @@ -255,6 +496,7 @@ Testing `checkInput` shows that it can indeed return multiple pieces of feedback ``` + +`checkInput` による入力のバリデーションは、`Monad` に対する `Applicative` の重要な利点を示しています。なぜなら、`>>=` は最初のステップの値に基づいて残りのプログラムを変更するのに十分なパワーを提供するため、最初のステップから渡される値を **受け取らなくてはならないからです** 。もし値を受け取らなかった場合(例えばエラーが発生した場合)、`>>=` はプログラムの残りの部分を実行することができません。`Validate` はどんな時でもプログラムの残りの部分を実行することが有用である理由を示しています:それ以前のデータが不要なケースでは、プログラムの残りの部分を実行することで有用な情報(この場合はより多くのバリデーションエラー)を得ることができます。`Applicative` の `<*>` は結果を再結合する前に両方の引数を実行することができます。同様に、`>>=` は逐次実行を強制します。各ステップは、次のステップを実行する前に完了しなければなりません。一般的にはこれは便利ですが、プログラムの実際のデータ依存関係から自然に生まれる異なるスレッドの並列実行を不可能にしてしまいます。`Monad` のようなより強力な抽象化によって、API利用者が利用できる柔軟性は向上しますが、API実装者が利用できる柔軟性は低下します。