カリー化された関数とカリー化されていない関数を理解できません。定義を提供しようとして Google で検索したすべてのサイトは、私には不明確でした。
ある例では、私は彼らが次のように言っているのを見つけました
max 4 5
と同じです(max 4) 5
しかし、私は彼らが何をしているのか理解できません。(max 4)
max に 2 つのパラメーターが必要な場合、どのように関数を作成できますか? 私は完全に迷っています。
Haskell の秘訣は、関数が 1 つの引数しかとらないことです。これは完全にクレイジーに思えますが、実際には機能します。
Haskell 関数:
foo :: Int -> Int -> Int
foo a b = a + b
本当の意味: 1 つの引数を取り、1 つの引数を取る別の関数を返す関数。これをカリー化といいます。
したがって、これを使用すると、この関数定義を次のように書くことができます。
foo :: Int -> (Int -> Int) --In math speak: right associative
と全く同じ意味です。
次のような簡潔なコードを記述できるようになったため、これは実際に非常に便利です。
foo1 :: Int -> Int
foo1 = foo 1
Haskell での関数適用は空白にすぎないため、ほとんどの場合、カリー化された関数はカリー化されていない (複数の引数を取り、結果を返すだけ) というふりをすることができます。
カリー化されていない関数が本当に本当に必要な場合: タプルを使用します。
uncFoo :: (Int, Int) -> Int
uncFoo (a, b) = a + b
編集
部分的なアプリケーションで何が起こっているかを理解するために、OK を検討してくださいbar
bar a b c = [a, b, c]
問題は、コンパイラがラムダに入力したものをこのように脱糖することです
bar = \a ->
\b ->
\c ->
[a, b, c]
これはクロージャーを利用します (各内部関数は前の引数への引数を「記憶」できます。
したがって、 と言うbar 1
と、コンパイラはbar
最も外側のラムダを調べて確認し、それを適用して次のようにします。
bar 1 = \b ->
\c ->
[1, b, c]
私たちが言うならbar 1 2
bar 1 2 = \c ->
[1, 2, c]
「適用する」という言葉の意味が漠然としている場合は、ラムダ計算からのベータ削減を本当に意味していることを知っておくと役立つかもしれません。
背景によっては、このペーパーが啓発的であることがわかるかもしれません: How to Make a Fast Curry: Push/Enter vs Eval Apply . 複数引数の関数は、単一のパラメーターをバインドして別の関数を返す関数として理解できるのは事実ですがmax = (\a -> (\b -> if a > b then a else b))
、実際の実装はかなり効率的です。
2 つの引数が必要であることをコンパイラが静的に認識している場合、max
コンパイラは常にmax 4 5
2 つの引数をスタック (またはレジスタ) にプッシュしてから を呼び出して変換しますmax
。これは基本的に、C コンパイラが変換する方法と同じmax(4, 5)
です。
一方、たとえばmax
が高階関数の引数である場合、コンパイラは引数の数を静的に認識できない場合がありmax
ます。おそらく、ある場合には 3 つかかるためmax 4 5
、部分的な適用になります。別の場合には、1 つだけを取って、適用さmax 4
れる新しい関数を生成し5
ます。この論文では、アリティが静的にわからない場合を処理するための 2 つの一般的なアプローチについて説明します。
あなたはおそらくすでにあなたの答えを得ていますが、繰り返します:
私たちが持っている場合
add x y = x + y
次に、次のように言うことができます。
add = \ x y -> x + y
add 3 = \ y -> 3 + y
add 3 5 = 3 + 5 = 8
あなたは「どうやっmax 3
て何かを計算できるのか」と尋ねると、答えは「できない」です。それはあなたに別の機能を与えるだけです。この関数は、呼び出すときに何かを実行できますが、すべての引数が指定されるまで、そのように「答えを得る」ことはありません。それまでは、関数を取得するだけです。
ほとんどの場合、これは便利な構文上のショートカットです。たとえば、あなたは書くことができます
uppercase :: String -> String
uppercase = map toUpper
言わなくてもいい
uppercase xs = map toUpper xs
引数が逆の場合map
、これを行うことはできません(_firstではなく、最後の引数のみをカレーすることができます)。したがって、関数を定義する順序を検討することが重要になる可能性があります。引数。
これはシンタックスシュガー以上のものなので、私は「ほとんどの場合」と言います。言語には、カリー化のために引数の数が異なる関数を多形的に処理できる場所がいくつかあります。すべての関数は、回答または別の関数を返します。リンクリスト(次のデータ項目またはリストの終わりマーカーを含む)のように考えると、これによって関数を再帰的に処理する方法がわかります。
それで、私はそれが一体何を意味するのでしょうか?たとえば、QuickCheckは、任意の数の引数を使用して関数をテストできます(各引数のテストデータを自動生成する方法がある場合)。これが可能なのは、関数型がカレーされているためです。すべての関数は、別の関数または結果を返します。リンクリストのように考えると、QuickCheckが引数がなくなるまで関数を再帰的に繰り返すことを想像できます。
次のコードスニペットは、アイデアを説明している場合と説明していない場合があります。
class Arbitrary a where
autogenerate :: RandomGenerator -> a
instance Arbitrary Int
instance Arbitrary Char
...
class Testable t where
test t :: RandomGenerator -> Bool
instance Testable Bool where
test rnd b = b
instance (Arbitrary a, Testable t) => Testable (a -> t) where
test rnd f = test $ f (autogenerate rnd)
関数がある場合foo :: Int -> Int -> Bool
、これはTestable
です。なんで?まあ、Bool
テスト可能です、したがって、そうですInt -> Bool
、そしてそれ故にそうですInt -> (Int -> Bool)
。
対照的に、タプルのサイズごとにサイズが異なるため、タプルのサイズごとに個別の関数(またはインスタンス)を作成する必要があります。タプルには再帰的な構造がないため、タプルを再帰的に処理することはできません。