11 に答える
F# 型
最初から始めましょう。
F# では、コロン ( :
) 表記を使用して、物事の種類を示します。type の値を定義するとしましょうint
:
let myNumber = 5
F# Interactive は がmyNumber
整数であることを認識し、次のように通知します。
myNumber : int
これは次のように読まれます
myNumber
タイプですint
F# 関数型
ここまでは順調ですね。他の何か、関数型を紹介しましょう。関数型は単に関数の型です。F# は->
関数型を表すために使用します。この矢印は、左側に書かれたものが右側に書かれたものに変換されることを象徴しています。
1 つの引数を取り、それを 1 つの出力に変換する単純な関数を考えてみましょう。このような関数の例は次のとおりです。
isEven : int -> bool
これにより、関数の名前 ( の左側:
) とその型が紹介されます。この行は英語で次のように読むことができます。
isEven
を に変換する関数型int
ですbool
。
言われていることを正しく解釈するには、「is of type」の部分の直後に短い間を置いてから、間を置かずに文の残りを一度に読む必要があることに注意してください。
F# では関数は値です
F# では、関数は (ほとんど)通常の型より特別なものではありません。それらは、bool、int、または文字列と同様に、関数に渡したり、関数から返したりできるものです。
あなたが持っている場合:
myNumber : int
isEven : int -> bool
とを同じ種類の 2 つのエンティティ、つまり型とint
見なす必要があります。int -> bool
ここで、myNumber
は type の値であり、 は typeint
のisEven
値です(これは、上記の短い一時停止int -> bool
について説明するときに象徴しようとしているものです)。
関数の適用
を含む型の値は、->
たまたまfunctionsとも呼ばれ、特別な力を持っています:関数を値に適用できます。たとえば、
isEven myNumber
isEven
は、呼び出された関数を value に適用していることを意味しますmyNumber
。の型を調べることで期待できるようにisEven
、ブール値が返されます。正しく実装していればisEven
、明らかに が返されfalse
ます。
関数型の値を返す関数
整数が他の整数の倍数であるかどうかを判断する汎用関数を定義しましょう。関数の型は次のようになると想像できます (括弧は理解を助けるためにここに付けられています。存在する場合も存在しない場合もあり、特別な意味があります)。
isMultipleOf : int -> (int -> bool)
ご想像のとおり、これは次のように読み取られます。
isMultipleOf
int
は、 を に変換する(PAUSE)関数に変換int
するタイプ (PAUSE) 関数ですbool
。
(ここで (PAUSE) は、声に出して読むときの一時停止を表します)。
この関数は後で定義します。その前に、使用方法を見てみましょう。
let isEven = isMultipleOf 2
F# Interactive は次のように答えます。
isEven : int -> bool
これは次のように読まれます
isEven
タイプですint -> bool
ここでは、値 2 ( ) をに与えたばかりなので、isEven
typeを持っています。これは、既に見たように、 anを anに変換します。int -> bool
int
isMultipleOf
int
int -> bool
この関数isMultipleOf
は、一種の関数作成者と見なすことができます。
の定義isMultipleOf
それでは、この神秘的な関数作成関数を定義しましょう。
let isMultipleOf n x =
(x % n) = 0
簡単でしょ?
これを F# Interactive に入力すると、次のように応答します。
isMultipleOf : int -> int -> bool
括弧はどこですか?
括弧がないことに注意してください。これは今のあなたにとって特に重要ではありません。矢印は右結合であることを覚えておいてください。つまり、持っている場合
a -> b -> c
あなたはそれを次のように解釈する必要があります
a -> (b -> c)
right in right 連想とは、右端の演算子の周りに括弧があるかのように解釈する必要があることを意味します。そう:
a -> b -> c -> d
と解釈する必要があります
a -> (b -> (c -> d))
の用途isMultipleOf
これまで見てきたように、 を使用isMultipleOf
して新しい関数を作成できます。
let isEven = isMultipleOf 2
let isOdd = not << isEven
let isMultipleOfThree = isMultipleOf 3
let endsWithZero = isMultipleOf 10
F# Interactive は次のように応答します。
isEven : int -> bool
isOdd : int -> bool
isMultipleOfThree : int -> bool
endsWithZero : int -> bool
しかし、あなたはそれを別の方法で使うことができます。新しい関数を作成したくない (または作成する必要がない) 場合は、次のように使用できます。
isMultipleOf 10 150
150 は 10 の倍数であるため、これは を返します。これは、関数を作成し、それを値 150 に適用することとtrue
まったく同じです。endsWithZero
実際には、関数の適用は左結合です。つまり、上記の行は次のように解釈する必要があります。
(isMultipleOf 10) 150
つまり、左端の関数適用を括弧で囲みます。
さて、これらすべてを理解できれば、あなたの例 (これは標準的CreateAdder
なものです) は自明なはずです!
少し前に、まったく同じ概念を扱うこの質問を誰かがしましたが、Javascript でした。私の答えでは、Javascript の 2 つの標準的な例 (CreateAdder、CreateMultiplier) を示します。これらは、関数を返すことについてより明確です。
これが役立つことを願っています。
これの標準的な例は、おそらく「加算器クリエーター」です。これは、数値 (たとえば 3) を指定すると、整数を取り、それに最初の数値を追加する別の関数を返す関数です。
したがって、たとえば、疑似コードでは
x = CreateAdder(3)
x(5) // returns 8
x(10) // returns 13
CreateAdder(20)(30) // returns 50
私は F# をチェックせずに書き込もうとするほど十分に快適ではありませんが、C# は次のようになります。
public static Func<int, int> CreateAdder(int amountToAdd)
{
return x => x + amountToAdd;
}
それは役に立ちますか?
編集: ブルーノが指摘したように、質問で指定した例は、まさに私が C# コードを指定した例であるため、上記の擬似コードは次のようになります。
let x = f1 3
x 5 // Result: 8
x 10 // Result: 13
f1 20 30 // Result: 50
これは、整数を取り、整数を取り、整数を返す関数を返す関数です。
これは、2 つの整数を取り、整数を返す関数と機能的に同等です。複数のパラメーターを受け取る関数を処理するこの方法は、関数型言語では一般的であり、値に関数を部分的に適用することを容易にします。
たとえば、2 つの整数を取り、それらを加算する add 関数があるとします。
let add x y = x + y
リストがあり、各項目に 10 を追加したいとします。add
function を value に部分的に適用します10
。パラメータの 1 つを 10 にバインドし、他の引数をバインドしないままにします。
let list = [1;2;3;4]
let listPlusTen = List.map (add 10)
このトリックにより、関数の作成が非常に簡単になり、非常に再利用しやすくなります。ご覧のとおり、リスト項目に 10 を追加して に渡す別の関数を作成する必要はありませんmap
。add
関数を再利用しました。
通常、これは 2 つの整数を取り、整数を返す関数として解釈されます。カリー化について読むべきです。
整数を取って整数を返す関数を返す、整数を取る関数
その最後の部分:
整数を取り、整数を返す関数
かなり単純なはずです。C# の例:
public int Test(int takesAnInteger) { return 0; }
だから私たちは残っています
返す整数を取る関数(上記のような関数)
C# 再び:
public int Test(int takesAnInteger) { return 0; }
public int Test2(int takesAnInteger) { return 1; }
public Func<int,int> Test(int takesAnInteger) {
if(takesAnInteger == 0) {
return Test;
} else {
return Test2;
}
}
あなたは読みたいかもしれません
F# (および他の多くの関数型言語) には、カリー化された関数と呼ばれる概念があります。これはあなたが見ているものです。基本的に、すべての関数は 1 つの引数を取り、1 つの値を返します。
これは最初は少し混乱するように思えます。なぜなら、書くことができlet add x y = x + y
、2 つの引数を追加するように見えるからです。しかし、実際には、元のadd
関数は引数のみを取りますx
。これを適用すると、1 つの引数 ( y
) を受け取り、x
値が既に入力されている関数が返されます。その後、その関数を適用すると、目的の整数が返されます。
これは型シグネチャに示されています。型シグネチャの矢印は、「左側のものを取り、右側のものを返す」という意味だと考えてください。typeint -> int -> int
では、これは型の引数 (整数) を取り、int
型の関数 (int -> int
整数を取り、整数を返す関数) を返すことを意味します。これは、上記のカリー化された関数がどのように機能するかの説明と正確に一致することに気付くでしょう。
これが私の2cです。デフォルトでは、F# 関数は部分適用またはカリー化を有効にします。これは、これを定義すると次のことを意味します。
let adder a b = a + b;;
と整数を取り、整数を取り、整数またはを返す関数を定義していますint -> int -> int
。カリー化すると、関数を部分的に適用して別の関数を作成できます。
let twoadder = adder 2;;
//val it: int -> int
上記のコードは、a を 2 に事前定義しているため、呼び出すたびtwoadder 3
に引数に 2 を追加するだけです。
関数パラメーターがスペースで区切られている構文は、次のラムダ構文と同等です。
let adder = fun a -> fun b -> a + b;;
これは、2 つの関数が実際に連鎖していることを理解するためのより読みやすい方法です。
例:
let f b a = pown a b //f a b = a^b
は int (指数) を取り、次のように引数をその指数まで上げる関数を返す関数です。
let sqr = f 2
また
let tothepowerofthree = f 3
それで
sqr 5 = 25
tothepowerofthree 3 = 27
この概念は高階関数と呼ばれ、関数型プログラミングでは非常に一般的です。
関数自体は、別のタイプのデータにすぎません。したがって、他の関数を返す関数を作成できます。もちろん、パラメーターとして int を受け取り、他の何かを返す関数を使用することもできます。2 つを組み合わせて、次の例を考えてみましょう (Python で)。
def mult_by(a):
def _mult_by(x):
return x*a
return mult_by
mult_by_3 = mult_by(3)
print mylt_by_3(3)
9
(pythonを使ってすみませんが、f#は知りません)
ここにはすでに多くの答えがありますが、別のテイクを提供したいと思います。同じことをさまざまな方法で説明すると、理解するのに役立つ場合があります。
私は関数を「あなたが私に何かをくれたら、私はあなたに何かを返す」と考えるのが好きです。
つまり、aFunc<int, string>
は「あなたは私に int を与え、私はあなたに文字列を与える」と言います。
また、「後で」という観点から考えた方が簡単だと思います。「intを与えると、文字列を与えます」。myfunc = x => y => x + y
これは、 (" Curriedにxを与えると、y を与えると x + y を返す何かが返される") のようなものを見る場合に特に重要です。
(ちなみに、ここでは C# に精通していることを前提としています)
int -> int -> int
したがって、あなたの例を として表現できますFunc<int, Func<int, int>>
。
私が考える別の方法 int -> int -> int
は、適切な型の引数を提供することによって、左から各要素をはがすことです。そして、->
's' がなくなったら、'laters' を終了し、値を取得します。
(楽しみのために)、すべての引数を一度に受け取る関数を、それらを「段階的に」受け取る関数に変換できます (それらを段階的に適用するための公式用語は「部分適用」です)。これは「カリー化」と呼ばれます。
static void Main()
{
//define a simple add function
Func<int, int, int> add = (a, b) => a + b;
//curry so we can apply one parameter at a time
var curried = Curry(add);
//'build' an incrementer out of our add function
var inc = curried(1); // (var inc = Curry(add)(1) works here too)
Console.WriteLine(inc(5)); // returns 6
Console.ReadKey();
}
static Func<T, Func<T, T>> Curry<T>(Func<T, T, T> f)
{
return a => b => f(a, b);
}