9

関数型プログラミング(OCaml)を学び始めましたが、fpに関する重要なトピックの1つである署名を理解していません(適切な名前かどうかはわかりません)。何かを入力してocamlでコンパイルすると、たとえば次のようになります。

# let inc x = x + 1 ;;
val inc : int -> int = <fun>

これは些細なことですが、なぜこれなのかわかりません。

let something f g a b = f a (g a b)

出力を与えます:

val something : (’a -> ’b -> ’c) -> (’a -> ’d -> ’b) -> ’a -> ’d -> ’c = <fun>

このトピックは多くの人にとって絶対にfpの基本であると思いますが、インターネット上でOCamlの署名について何も見つけられなかったので、ここで助けを求めます(Haskellの署名に関する記事はいくつかありますが、説明はありません) )。

このトピックがどういうわけか生き残るのであれば、私はここにいくつかの関数を投稿しますが、その署名は私を混乱させました:

# let nie f a b = f b a ;; (* flip *)
val nie : (’a -> ’b -> ’c) -> ’b -> ’a -> ’c = <fun>

# let i f g a b = f (g a b) b ;;
val i : (’a -> ’b -> ’c) -> (’d -> ’b -> ’a) -> ’d -> ’b -> ’c = <fun>


# let s x y z = x z (y z) ;;
val s : (’a -> ’b -> ’c) -> (’a -> ’b) -> ’a -> ’c = <fun>

# let callCC f k = f (fun c d -> k c) k ;;
val callCC : ((’a -> ’b -> ’c) -> (’a -> ’c) -> ’d) -> (’a -> ’c) -> ’d = <fun>

ヘルプと説明をありがとう。

4

2 に答える 2

18

この型注釈を理解するために理解する必要のある概念がいくつかありますが、どの概念をすでに実行しているのかわかりません。そのため、すべての重要な概念を説明するために最善を尽くしました。

カリー化

ご存知のように、型がある場合foo -> bar、これは型の引数を取り、型fooの結果を返す関数を記述しますbar->は右結合法則であるため、型はfoo -> bar -> bazと同じでfoo -> (bar -> baz)あり、型の引数を取り、型fooの値を返す関数を記述しますbar -> baz。つまり、戻り値は型の値を取り、型の値を返す関数barですbaz

このような関数は、のように呼び出すことができますmy_function my_foo my_bar。これは、関数適用が左結合であるため、と同じです(my_function my_foo) my_bar。つまり、引数に適用され、結果として返される関数をmy_function引数に適用します。my_foomy_bar

このように呼び出すことができるため、型の関数はfoo -> bar -> baz「2つの引数を取る関数」と呼ばれることが多く、この回答の残りの部分でそうします。

型変数

のような関数を定義するlet f x = xと、タイプはになり'a -> 'aます。しかし'a、実際にはOCaml標準ライブラリのどこにもタイプが定義されていないので、それは何ですか?

aで始まる型'は、いわゆる型変数です。型変数は、可能な任意の型を表すことができます。したがって、上記の例では、または、または、または何でもf呼び出すことができます-それは問題ではありません。intstringlist

さらに、同じ型変数が型シグネチャに複数回出現する場合、それは同じ型を表します。したがって、上記の例では、の戻り型がf引数型と同じであることを意味します。したがって、fがで呼び出された場合はint、を返しますint。で呼び出された場合stringは、などを返しますstring

したがって、型の関数は'a -> 'b -> 'a任意の型の2つの引数(最初と2番目の引数で同じ型ではない可能性があります)を'a -> 'a -> 'a取り、最初の引数と同じ型の値を返しますが、型の関数は同じタイプ。

型推論に関する1つの注意:関数に型シグネチャを明示的に与えない限り、OCamlは常に可能な限り最も一般的な型を推論します。したがって、関数が特定の型(たとえば)でのみ機能する操作を使用しない限り、+推論された型には型変数が含まれます。

次にタイプを説明します...

val something : ('a -> 'b -> 'c) -> ('a -> 'd -> 'b) -> 'a -> 'd -> 'c = <fun>

この型アノテーションは、それsomethingが4つの引数を取る関数であることを示しています。

最初の引数のタイプはです'a -> 'b -> 'c。つまり、任意の、場合によっては異なるタイプの2つの引数を取り、任意のタイプの値を返す関数です。

2番目の引数のタイプはです'a -> 'd -> 'b。これも2つの引数を持つ関数です。ここで重要なのは、関数の最初の引数は最初の関数の最初の引数と同じ型である必要があり、関数の戻り値は最初の関数の2番目の引数と同じ型である必要があるということです。

3番目の引数の型は'a、です。これは、両方の関数の最初の引数の型でもあります。

最後に、4番目の引数'dの型は、2番目の関数の2番目の引数の型です。

戻り値はタイプ'c、つまり最初の関数の戻りタイプになります。

于 2010-11-17T00:43:11.293 に答える
6

このテーマに本当に興味がある(そして大学図書館にアクセスできる)場合は、Wadlerの優れた(多少古い場合は)「機能プログラミング入門」をお読みください。型シグネチャと型推論を非常にわかりやすく説明しています。

さらに2つのヒント:->矢印は右連想であるため、右から括弧で囲むことができます。これは、物事を理解するのに役立つ場合があります。つまりa -> b -> c、と同じa -> (b -> c)です。これは、2番目のヒントである高階関数に関連しています。あなたは次のようなことをすることができます

let add x y = x + y
let inc = add 1

したがって、FPでは、「add」を2つの数値パラメーター取り、数値を返す必要がある関数として考えることは、一般的に正しいことではありません。1つの数値引数を取り、型を持つ関数を返す関数でもあります。 num->num。

これを理解すると、型シグネチャを理解するのに役立ちますが、それがなくても実行できます。ここでは、すばやく簡単に:

# let s x y z = x z (y z) ;;
val s : (’a -> ’b -> ’c) -> (’a -> ’b) -> ’a -> ’c = <fun>

右側を見てください。yには1つの引数が与えられるためa -> b、aは。の型ですzxには2つの引数が与えられ、z最初の引数はです。したがって、最初の引数の型aも同様である必要があります。(y z)2番目の引数であるのタイプはです。bしたがって、のタイプはxです(a -> b -> c)sこれにより、タイプをすぐに推測できます。

于 2010-11-17T00:42:33.177 に答える