12

Haskell と関数型プログラミングは初めてです。Real World Haskell を読んでいて、いくつかの例に混乱していることに気付きました。

具体的には、第 9 章の「述語用のドメイン固有言語」セクションに、wxyz パラメータを持つ例があります。

私はこれに要約しました:

このコードがコンパイルされるのはなぜですか?

f :: Int -> (Int -> Int)
f x y = x+y

main = do
  let q = f 4 5
  putStr  (show (q))

型シグネチャによると、f明らかに 1 つのパラメーターを受け取り、関数を返します。ただし、2 つのパラメーターを受け入れて int を返すように、関数方程式を記述できるようです。なぜこれが可能なのですか?これは、型シグネチャが無視されることを意味しますか?

これはカレーですか?これはある種の閉鎖ですか?このhttp://www.haskell.org/haskellwiki/Curryingを正しく理解していれば、そこで定義されているカリー化とは逆のように思えます。私のf関数は単一の引数ではなく複数の引数を取ります!

また、誰でも回答できますか?この機能が記載されているある種のHaskellドキュメントへのリンクを提供してください(可能であれば)。

編集:

このことについてしばらく考えた後、お二人が暗示しているように思われるのは、次のことです。

1) この構文はシンタックス シュガーです。式にいくつのパラメーターが記述されていても、f は常に 1 つのパラメーターを持ちます。

2) f を適用すると、関数本体は (常に?) スタブ (実際には、返される関数) に変換されます。ここで、x は (4) で指定されたパラメーターに固定され、y はパラメーターです。

3) 次に、この新しい関数が 5 に適用され、y が置き換えられ、+ 関数が評価されます。

私が本当に興味を持ったのは、上で書いたように、「関数方程式で、複数のパラメーターを記述すると、実際には構文糖衣であり、次のことが実際に発生します...」のようなことを正確にどこで言っているのかということでした。それとも、それは私以外の誰にとっても明らかですか?

編集 II:

本当の目を見張るような答えは、以下の @luqui コメントにありました。残念ながら、コメントを答えとしてマークすることはできないと思います。

fxy = ... は実際には次の構文糖衣であるという事実です: f = \x -> \y -> ...

そして、私にとって、以下のすべての人が言ったことはすべてこれからです。

Haskellのジェントルイントロダクションで、これに関する一種のソースを見つけました.http: //haskell.cs.yale.edu/tutorial/functions.htmlのセクション3.1で、ラムダ抽象化と呼ばれています。

実際、方程式は次のとおりです。

inc x = x+1 add xy = x+y

は、次の略記です。

inc = \x -> x+1 add = \xy -> x+y

「シンタックス シュガー」という語句は使用していませんが、数学的な意味合いの強い「速記」という言葉を使用していますが、プログラマーとして私はこれを「シュガー」と読みます :-)

4

5 に答える 5

10

カレーです。型シグネチャが示すように、fは 1 つのパラメーターのみを取ります。それはそうでしょう4。次に、すぐに に適用される関数を返します5。実際、これら 2 つの型シグネチャは次のとおりです。

Int -> Int -> Int

Int -> (Int -> Int)

Haskell では同等です。

編集:あなたが提供したページ内で見つけたPartial Applicationに関するこのリンクは、これを説明しています。

編集 2: Haskell のカリー化動作が定義されている場所を尋ねました。これがあなたが探しているものかどうかはわかりません: Haskell 98 Revised Reportのセクション3.3 Curried Applications and Lambda Abstractionsには、次のように書かれています:

関数の適用が書かれてe1 e2います。アプリケーションは左側に関連付けられるため、 では括弧を省略できます(f x) y

于 2011-01-22T14:50:41.013 に答える
4

->演算子は右結合です。つまり、t -> t -> tと同じt -> (t -> t)です。

書き直せば

f x y = x+y

同等の形式で

f x = \y -> x + y

この考えは明らかになるはずです。

于 2011-01-22T16:01:42.660 に答える
2

それは実際にはシンタックスシュガーではなく、Haskellで関数適用がどのように機能するかです。

検討:

f :: Int -> Int -> Int -> Int 
f x y z = x + y + z

g = f 4
h = g 4 5

f 4 4 5 -- 13
g 4 5   -- 13
g 6     -- 13

確認のためにghciでこれを試してみることができます。g関数の部分適用ですf-そのタイプはg :: Int -> Int -> Intです。

次のように書くこともできます。

((f 4) 4) 5  -- 13 

この場合(f 4)、2つの追加の引数を取る部分的に適用された関数を((f 4) 4)返し、1つの引数を取る部分的に適用された関数を返し、式全体が13になります。

于 2011-01-22T22:22:47.983 に答える
2

これについてもう少し考えた後、完全な説明は次のようなものになるはずです。

Haskell 関数は、引数を 1 つだけ受け取り、パラメーターを 1 つ返すことができます。Haskell では、複数の引数が渡されるふりをすることができますが、この形式は一連のネストされたラムダ関数として扱われます。

f x y = x + y

として扱われます

(1) f = \x -> \y -> x + y

この扱いはラムダ関数にも当てはまります \xy -> x + y は \x -> \y -> x + y として扱われます

これにより、型宣言を左結合として扱うことができます。つまり、 f :: Int -> Int -> Int は実際には f :: (Int -> (Int -> Int)) であり、上記の (1) に正確に適合します。 f には引数がありませんが、Int を受け入れる関数を返しています。この関数は、別の Int を受け入れる関数を返し、Int を返します。

これは、関数から関数を返したい場合、Haskell の「デフォルト」モードであるため、特別なことをする必要がないことを意味します。

これは、型宣言 f :: Int -> Int -> Int が与えられることも意味します。 f の実装 (「式」) を 0、1、または 2 つのパラメーターで記述できます。1 つまたは 2 つのパラメーターが指定されている場合、コンパイラーは f :: (Int -> (Int -> Int)) の形式に準拠するために必要なラムダを生成します。

f = \x -> \y -> x + y

f x = \y -> x + y  -- \x -> is generated

f x y = x + y -- \x -> \y is generated

しかし、これらのケースのそれぞれで、2 つのパラメーターを受け入れるように見える関数アプリケーションは、常に最初の形式に変換されるため、正常にコンパイルされます。

f 4 5 --> (\x -> (\y -> x + y) 5 ) 4

内部関数アプリケーションがカリー化された形式 (x + 5) を返す場所

これにより、部分関数の適用が可能になり、f にパラメーターを 1 つだけ与えるだけで部分関数を取得できます。

また、関数型の優先順位を変更します。

f'' :: (Int -> Int) -> Int

意味が変わります - Int を取得してそれを返す関数を単一のパラメーターとして受け入れ、Int を返します。
この関数をどこかで定義したと仮定して、この関数を整数パラメータで呼び出します。

f'' 4 5

コンパイルされません。

編集:

また、最後の引数が括弧内または型宣言であっても、これは成り立ちます。

たとえば、次の例では、括弧の最後のペアはオプションです。これらが存在しない場合、コンパイラは「ラムダ」形式に到達するためにそれらを配置するためです。

t4 :: (Int -> Int) -> (Int -> Int)
t4 f i = f i + i

次のように適用できます。

t4 (\x -> x*10) 5

また、与えられた:

type MyIntInt = Int -> Int

それで:

t5 :: MyIntInt -> MyIntInt

t4 と同等ですが、MyIntInt の意味が両方の場所で異なるため、紛らわしいです。最初のものは、最初のパラメーターの型です。
2 番目のものは Int -> Int に「展開」されます (おそらく演算子の右結合のため)。これは、t5 が関数方程式と関数適用で 0 から 2 個のパラメーターを受け入れるように見えることを意味します (実際には常に 0 を受け入れます)。上記で詳しく説明したように、埋め込まれたラムダを返します)。

たとえば、t4 と同じように t5 を書くことができます。

t5 f i = f i + i
于 2011-01-23T10:32:41.800 に答える
2

これは間違いなくちょっとしたカレーです。ドキュメントのどこにこの動作が明示的に記載されているかはすぐにはわかりませんが、カリー化に関する数学を少し調べるだけで済みます。

ご存知のように、シグネチャInt ->(Int -> Int)を持つ関数は を取り、 を取り、Int を返すInt関数を返します。そして、あなたの例のように、最終的な int を取得するために必要な s のInt両方を提供することができます:Int

f :: Int -> (Int -> Int)
f x y = x+y

最初の引数だけを指定すると、別の引数を必要とする関数が返されます。カレーの基本。

簡単に言えば、カリー化は右結合です。つまり、Int -> (Int -> Int)は と同じですが、次のInt->Int->Intことをより明確にするために括弧を追加しただけです。

f 3

引数が欠落していませんが、実際には type の関数を返しますInt->Int

足し算の連想特性を学ぶとき、それは数学に戻ったようなものです。

3 + 2 + 1 = (3 + 2) + 1 = 3 + (2 + 1)

括弧をどのように配置しても、結果は同じです。

于 2011-01-22T20:58:23.543 に答える