17

私はHaskellとFP全般に非常に慣れていません。カリー化とは何かを説明する多くの著作を読みましたが、それが実際にどのように機能するかについての説明は見つかりませんでした。

ここに関数があります: (+) :: a -> (a -> a) If I do なら(+) 4 7、関数は4を受け取って返す関数を受け取っ7て返します11。しかし、どうなり4ますか?その最初の関数は何をし4ますか? とは何(a -> a)ですか7

より複雑な関数について考えると、事態はさらに混乱します。

max' :: Int -> (Int -> Int)
max' m n | m > n = m
         | otherwise = n

そのパラメータを何と(Int -> Int)比較しますか? パラメータは 1 つしか必要ありませんが、実行するには 2 つ必要ですm > n

4

5 に答える 5

18

高階関数を理解する

Haskell は関数型言語として、高階関数 (HOF) をサポートしています。数学では、HOF は汎関数と呼ばれますが、それらを理解するために数学は必要ありません。Java のような通常の命令型プログラミングでは、関数は整数や文字列などの値を受け取り、それらに対して何らかの処理を行い、他の型の値を返すことができます。

しかし、関数自体が値と変わらず、関数を引数として受け入れたり、別の関数から返すことができるとしたら? f a b c = a + b - cは退屈な関数で、合計abてから減算しcます。しかし、この関数を一般化できれば、もっと興味深い関数になる可能性abあります。cそれとも引く代わりに割りますか?

は、数値を返す 2 つの数値の関数にすぎないことを覚えておいてください。(+)特別なことは何もないので、数値を返す 2 つの数値の任意の関数を代わりに使用できます。などを書くことはg a b c = a * b - ch a b c = a + b / c私たちにとってはうまくいきません。一般的な解決策が必要です。結局のところ、私たちはプログラマーなのです! Haskell での方法は次のとおりです。

let f g h a b c = a `g` b `h` c in f (*) (/) 2 3 4 -- returns 1.5

また、関数を返すこともできます。以下では、関数と引数を受け取り、パラメーターを受け取り、結果を返す別の関数を返す関数を作成します。

let g f n = (\m -> m `f` n); f = g (+) 2 in f 10 -- returns 12

(\m -> m `f` n)コンストラクトは、それに適用される1つの引数の無名関数です。基本的に、呼び出すときは引数が 1 つの関数を作成します。これは、受け取ったものに 2 を追加するだけです。したがって、12 に等しく、25 に等しくなります。mfmng (+) 2let f = g (+) 2 in f 10let f = g (*) 5 in f 5

( Scheme を例として使用したHOF の説明も参照してください。)

カリー化を理解する

カリー化とは、複数の引数の関数を、1 つの引数の関数を返す 1 つの引数の関数を返す 1 つの引数の関数に変換する手法です...値が返されるまで。思ったより簡単です。たとえば、 のような 2 つの引数の関数があります(+)

引数を 1 つだけ指定すると、関数が返されることを想像してみてください。後でこの関数を使用して、この新しい関数に入れられたこの最初の引数を別のものに追加できます例えば:

f n = (\m -> n - m)
g = f 10
g 8 -- would return 2
g 4 -- would return 6

Haskell はデフォルトですべての関数をカリー化します。技術的に言えば、Haskell には複数の引数の関数はなく、1 つの引数の関数のみがあり、そのうちのいくつかは 1 つの引数の新しい関数を返す場合があります。

それは種類から明らかです。:t (++)インタプリタで記述します。(++)は 2 つの文字列を連結する関数で、 を返し(++) :: [a] -> [a] -> [a]ます。型は ではありません[a],[a] -> [a][a] -> [a] -> [a]、つまり、(++)1 つのリストを受け入れ、型の関数を返します[a] -> [a]。この新しい関数はさらに別のリストを受け入れることができ、最終的に type の新しいリストを返し[a]ます。

そのため、Haskell の関数適用構文には括弧とコンマがありません。Haskell のf a b cものと Python や Java のものを比較してくださいf(a, b, c)。Haskell 関数アプリケーションでは、左から右に進むため、f a b c実際には です。これは、デフォルトでカリー化され(((f a) b) c)ていることを知っていれば、完全に理にかなっています。f

ただし、型では、関連付けは右から左に行われるため、[a] -> [a] -> [a]と同等[a] -> ([a] -> [a])です。これらは Haskell では同じものであり、Haskell はそれらをまったく同じように扱います。引数を 1 つだけ適用すると、 type の関数が返されるため、これは理にかなっています[a] -> [a]

一方、map:の型を確認してください(a -> b) -> [a] -> [b]。最初の引数として関数を受け取ります。そのためかっこが付いています。

カリー化の概念を実際に掘り下げるには、インタープリターで次の式の型を見つけてみてください。

(+)
(+) 2
(+) 2 3
map
map (\x -> head x)
map (\x -> head x) ["conscience", "do", "cost"]
map head
map head ["conscience", "do", "cost"]

部分適用とセクション

HOF とカリー化を理解したところで、コードを短くするための構文が Haskell から提供されます。1 つまたは複数の引数を持つ関数を呼び出して、引数を受け入れる関数を取得する場合、それは部分適用と呼ばれます。

匿名関数を作成する代わりに関数を部分的に適用できることは既に理解してい(\x -> replicate 3 x)ます(replicate 3)(/)しかし、の代わりに除算演算子が必要な場合はどうすればよいでしょうreplicateか? 中置関数の場合、Haskell では、いずれかの引数を使用して部分的に適用できます。

これはセクションと呼ばれます:(2/)と同等で(\x -> 2 / x)あり、(/2)と同等(\x -> x / 2)です。バッククォートを使用すると、任意のバイナリ関数のセクションを取得できます:(2`elem`)は と同等(\xs -> 2 `elem` xs)です。

しかし、Haskell ではどの関数もデフォルトでカリー化されているため、常に 1 つの引数を受け入れるため、セクションは実際にはどの関数でも使用できます: 4 つの引数を合計して14 を返す(+^)奇妙な関数を考えてみましょう。let (+^) a b c d = a + b + c in (2+^) 3 4 5

組成物

簡潔で柔軟なコードを書くためのその他の便利なツールは、合成アプリケーション演算子です。構成演算子(.)は、関数をまとめてチェーンします。適用演算子($)は、左側の関数を右側の引数に適用するだけなので、f $ xと同等f xです。ただし($)、すべての演算子の中で優先順位が最も低いため、これを使用して括弧を取り除くことができます:f (g x y)は と同等f $ g x yです。

同じ引数に複数の関数を適用する必要がある場合にも役立ちます: map ($2) [(2+), (10-), (20/)]would yield [4,8,10]. (f . g . h) (x + y + z)f (g (h (x + y + z)))f $ g $ h $ x + y + zおよびf . g . h $ x + y + zは同等ですが、(.)($)は異なるものです。Haskell: difference between をお読みください。Learn You a Haskell の(ドット) と $ (ドル記号)パーツの違いを理解してください。

于 2015-02-19T20:50:18.070 に答える
14

関数が引数を格納し、他の引数を要求するだけの新しい関数を返すように考えることができます。新しい関数は、最初の引数を関数と一緒に格納しているため、既に知っています。これは、コンパイラによって内部的に処理されます。これがどのように機能するかを正確に知りたい場合は、このページに興味があるかもしれませんが、Haskell を初めて使用する場合は少し複雑になるかもしれません。

関数呼び出しが完全に飽和している場合 (つまり、すべての引数が同時に渡される場合)、ほとんどのコンパイラは C のような通常の呼び出しスキームを使用します。

于 2011-07-11T15:36:22.903 に答える
13

これは役に立ちますか?

max' = \m -> \n -> if (m > n)
                       then m
                       else n

ラムダとして記述されます。max'はラムダの値であり、mが与えられると、それ自体がラムダを返し、値を返します。

したがって、max'4は

max' 4 = \n -> if (4 > n)
                   then 4
                   else n
于 2011-07-11T15:28:13.153 に答える
3

あなたがCのような言語から来ているなら、それらの構文はあなたがそれを理解するのを助けるかもしれません。たとえば、PHPでは、add関数は次のように実装できます。

function add($a) {
    return function($b) use($a) {
         return $a + $b;
    };
}
于 2011-07-12T07:54:41.590 に答える
3

Haskell にカリーのサポートが組み込まれていない場合、カリーを高階関数として実装する方法を考えてみるとよいでしょう。これは、2 つの引数の関数に対して機能する Haskell の実装です。

curry :: (a -> b -> c) -> a -> (b -> c)
curry f a = \b -> f a b

curryこれで、2 つの引数と最初の引数で関数を渡すことができ、1 つの引数で関数が返されます (これはクロージャの例です)。

ghci で:

Prelude> let curry f a = \b -> f a b
Prelude> let g = curry (+) 5
Prelude> g 10
15
Prelude> g 15
20
Prelude> 

幸いなことに、サポートが言語に組み込まれているため、Haskell でこれを行う必要はありません (カリー化が必要な場合は Lisp で行います)。

于 2011-07-11T17:19:55.157 に答える