これはどのように可能ですか、そこで何が起こっているのですか?
これには名前がありますか?
他のどの言語がこれと同じ振る舞いをしていますか?
強いタイピングシステムがないものはありますか?
5 に答える
タイプを見ると、この動作は本当にシンプルで直感的です。のような中置演算子の複雑さを避けるために、代わり+
に関数を使用します。また、型クラスのラインノイズを減らすために、にのみ取り組むplus
ことに特化します。plus
Int
plus
タイプの関数があるとしますInt -> Int -> Int
。Int
それを読む1つの方法は、「を返す2つのsの関数Int
」です。しかし、その表記はその読書には少し不器用ですよね?リターンタイプは、特にどこでも選択されていません。なぜこのように関数型シグネチャを書くのでしょうか?->
は正しい結合法則であるため、同等のタイプはになりますInt -> (Int -> Int)
。Int
これは、「からへの関数(からへの関数Int
) 」と言っているように見えますInt
。しかし、これら2つのタイプは実際にはまったく同じであり、後者の解釈は、この動作がどのように機能するかを理解するための鍵となります。
Haskellは、すべての関数を単一の引数から単一の結果までのものと見なしています。計算の結果が2つ以上の入力(などplus
)に依存する場合、念頭に置いている計算があるかもしれません。Haskellによれば、この関数 plus
は単一の入力を受け取り、別の関数である出力を生成する関数です。この2番目の関数は、単一の入力を受け取り、数値である出力を生成します。2番目の関数は最初に計算されたため(最初の関数への入力が異なると異なります)、「最終的な」出力は両方の入力に依存する可能性があります。したがって、単一の入力のみを使用するこれらの関数を使用して、複数の入力で計算を実装できます。 。
タイプを見れば、これは本当にわかりやすいと約束しました。型に明示的に注釈が付けられた式の例を次に示します。
plus :: Int -> Int -> Int
plus 2 :: Int -> Int
plus 2 3 :: Int
何かが関数であり、それを引数に適用する場合、そのアプリケーションの結果の型を取得するには、関数の型から最初の矢印までのすべてを削除するだけです。それがより多くの矢印を持つタイプを残す場合、あなたはまだ関数を持っています!式の右側に引数を追加すると、その型の左側からパラメーター型が削除されます。この型により、すべての中間結果の型が何であるか、plus 2
さらに適用できる関数(その型には矢印があります)とplus 2 3
そうでない関数(その型には矢印がありません)がなぜあるのかがすぐにわかります。
「カリー化」とは、2つの引数の関数を、元の関数が返したものを返す別の引数の関数を返す1つの引数の関数に変換するプロセスです。また、すべての関数が自動的にこのように機能するHaskellのような言語のプロパティを参照するためにも使用されます。人々はHaskellが「カリー化された言語である」または「カリー化されている」または「カリー化された機能を持っている」と言うでしょう。
Haskellの関数適用の構文は単純なトークン隣接であるため、これは特にエレガントに機能することに注意してください。to 2引数の適用、またはtoの適用とその後の結果の適用plus 2 3
として自由に読むことができます。あなたはそれをあなたがその時にしていることに最も合う方法でそれを精神的にモデル化することができます。plus
plus
2
3
括弧で囲まれた引数リストによるCのような関数適用を持つ言語では、これは少し壊れます。plus(2, 3)
はとは大きく異なり、plus(2)(3)
この構文の言語では、関連する2つのバージョンのplus
タイプが異なる可能性があります。そのため、この種の構文を使用する言語では、すべての関数が常にカリー化されるとは限らず、必要な関数が自動的にカリー化されることさえありません。しかし、そのような言語は歴史的にファーストクラスの値としての機能を持たない傾向があり、それがカリー化の欠如を論点にしています。
Haskellでは、すべての関数が正確に1つの入力を受け取り、正確に1つの出力を生成します。関数への入力または関数の出力が別の関数である場合があります。関数への入力または関数の出力もタプルにすることができます。次の2つの方法のいずれかで、複数の入力を持つ関数をシミュレートできます。
タプルを入力として使用する
(in1, in2) -> out
関数を出力として使用する*
in1 -> (in2 -> out)
同様に、次の2つの方法のいずれかで、複数の出力を持つ関数をシミュレートできます。
タプルを出力として使用する*
in -> (out1, out2)
関数を「2番目の入力」として使用する(出力としての関数)
in -> ((out1 -> (out2 -> a)) -> a)
*この方法は通常Haskellersに好まれます
この(+)
関数は、関数を出力として生成する典型的なHaskellの方法で2つの入力を取得することをシミュレートします。(コミュニケーションを容易にするために特化Int
:)
(+) :: Int -> (Int -> Int)
便宜上、->
は右結合であるため、の型署名(+)
も記述できます。
(+) :: Int -> Int -> Int
(+)
は数値を取り込んで、数値から数値へと別の関数を生成する関数です。
(+) 5
(+)
は引数に適用した結果である5
ため、数値から数値への関数です。
(5 +)
書く別の方法です(+) 5
2 + 3
別の書き方(+) 2 3
です。関数適用は左結合であるため、これは別の記述方法です(((+) 2) 3)
。言い換えると、関数(+)
を入力に適用します2
。結果は関数になります。その関数を取り、それを入力に適用します3
。その結果が数字です。
したがって、(+)
は関数で(5 +)
あり、は関数であり、(+) 2 3
は数値です。
Haskellでは、2つの引数の関数を取り、それを1つの引数に適用し、1つの引数の関数を取得できます。実際、厳密に言えば、+は2つの引数の関数ではなく、1つの引数の関数を返す1つの引数の関数です。
- 素人の用語では、
+
は実際の関数であり、返されるまで特定の数のパラメーター(この場合は2つ以上)を受け取るのを待っています。2つ以上のパラメーターを指定しない場合、別のパラメーターを待機する関数のままになります。 - それはカリー化と呼ばれています
- 多くの関数型言語(Scala、Schemeなど)
- ほとんどの関数型言語は強く型付けされていますが、これはエラーを減らすので最終的には優れています。これはエンタープライズシステムや重要なシステムでうまく機能します。
ちなみに、Haskellという言語は、コンビネータ論理に取り組んでいるときに関数型カリー化の現象を再発見したHaskellCurryにちなんで名付けられました。
HaskellやOCamlのような言語は、特にカリー化に適した構文を持っていますが、Schemeでのカリー化のように、動的型付けを使用して他の言語でそれを行うことができます。