サーチ…


複数のパラメータの関数

F#では、 すべての関数がちょうど1つのパラメータをとります 。これは、関数宣言で複数のパラメータを宣言するのが簡単であるため、奇妙に思えます。

let add x y = x + y

しかし、その関数宣言をF#インタラクティブなインタプリタに入力すると、その型シグネチャは次のようになります。

val add : x:int -> y:int -> int

パラメータ名がなければ、そのシグネチャはint -> int -> intです。 ->演算子は右結合です。つまり、このシグネチャはint -> (int -> int)と等価です。言い換えれば、 add 1つの取る関数であるintパラメータを、 一つ取る関数を返すintと返すint 。それを試してみてください:

let addTwo = add 2
// val addTwo : (int -> int)
let five = addTwo 3
// val five : int = 5

しかし、 addような関数を "従来の"方法で呼び出すこともできます。これは直接2つのパラメータを渡すことで、期待通りに動作します:

let three = add 1 2
// val three : int = 3

これは、必要な数のパラメータを持つ関数に適用されます。

let addFiveNumbers a b c d e = a + b + c + d + e
// val addFiveNumbers : a:int -> b:int -> c:int -> d:int -> e:int -> int
let addFourNumbers = addFiveNumbers 0
// val addFourNumbers : (int -> int -> int -> int -> int)
let addThreeNumbers = addFourNumbers 0
// val addThreeNumbers : (int -> int -> int -> int)
let addTwoNumbers = addThreeNumbers 0
// val addTwoNumbers : (int -> int -> int)
let six = addThreeNumbers 1 2 3  // This will calculate 0 + 0 + 1 + 2 + 3
// val six : int = 6

この方法は、複数のパラメータ関数を1つのパラメータをとり、新しい関数を返す関数であると考える方法です(最後のパラメータを取得して最終的に結果を返す最終関数に到達するまで、このコンセプトを開発することで有名な数学者のハスケルカリーに敬意を表して、 カリングと呼ばれています 。 (これは他の誰かによって発明されたものですが、カレーはその価値を最大限に引き出しています。)

この概念はF#全体で使用されています。あなたはそれに精通したいでしょう。

機能の基礎

F#のほとんどの関数はlet構文で作成されます:

let timesTwo x = x * 2

これはtimesTwoという名前の関数を定義し、単一のパラメータxをとります。あなたは(対話型のF#セッションを実行する場合fsharpi OS XおよびLinux上、 fsi.exe Windowsの場合)とで、その関数を貼り付け(および追加;;伝えfsharpiあなただけの入力したコードを評価するために)、あなたはそれがわかります返信先:

val timesTwo : x:int -> int

これは、ということを意味timesTwo単一のパラメータ取る関数であるx型のint 、そして返しint 。関数シグネチャはパラメータ名なしで記述されることが多いので、この関数型はint -> int書かれていることがよくあります。

ちょっと待って!どのようにして型を指定したことがないので、 xが整数パラメータであったことをF#はどのように知っていましたか?それは型推論のためです。関数本体ではx2を掛けたので、 x2型は同じでなければなりません。 (一般的なルールとして、F#は暗黙的に異なる型に値をキャストしません;必要な型キャストを明示的に指定する必要があります)。

パラメータを取らない関数を作成したい場合は、 間違った方法です:

let hello =  // This is a value, not a function
    printfn "Hello world"

これを行う正しい方法は次のとおりです。

let hello () =
    printfn "Hello world"

このhello関数は、 unit -> unit型を持ちます。これについては、 "unit"型で説明しています。

CurriedとTupledの関数

F#、Curried関数、およびTupled関数で複数のパラメータを持つ関数を定義するには、2つの方法があります。

let curriedAdd x y = x + y // Signature: x:int -> y:int -> int
let tupledAdd (x, y) = x + y // Signature:  x:int * y:int -> int

F#の外部で定義されたすべての関数(.NETフレームワークなど)は、F#でTupled形式で使用されます。 F#コアモジュールのほとんどの機能は、Curried形式で使用されます。

Curriedフォームは部分的な適用が可能なので、慣用的なF#と見なされます。 Tupled形式では、次の2つの例はどちらも使用できません。

let addTen = curriedAdd 10 // Signature: int -> int

// Or with the Piping operator
3 |> curriedAdd 7 // Evaluates to 10

その理由は、Curried関数が1つのパラメータで呼び出されたときに関数を返すためです。関数型プログラミングへようこそ!!

let explicitCurriedAdd x = (fun y -> x + y) // Signature: x:int -> y:int -> int
let veryExplicitCurriedAdd = (fun x -> (fun y -> x + y)) // Same signature

それは全く同じ署名であることがわかります。

ただし、ライブラリを書くときのように、他の.NETコードとインターフェイスするときは、Tupledフォームを使用して関数を定義することが重要です。

インライン展開

インライン化では、関数呼び出しを関数本体に置き換えることができます。

これは、コードの重要な部分でパフォーマンス上の理由から役立つことがあります。しかし、関数の本体は呼び出しが発生したどこにでも複製されるので、アセンブリは多くのスペースを必要とします。関数をインライン化するかどうかを決めるときは注意が必要です。

inlineキーワードで関数をインライン化することができます:

// inline indicate that we want to replace each call to sayHello with the body 
// of the function
let inline sayHello s1 =
    sprintf "Hello %s" s1

// Call to sayHello will be replaced with the body of the function
let s = sayHello "Foo"
// After inlining -> 
// let s = sprintf "Hello %s" "Foo"

printfn "%s" s
// Output
// "Hello Foo"

ローカル値を持つ別の例:

let inline addAndMulti num1 num2 =
    let add = num1 + num2
    add * 2

let i = addAndMulti 2 2
// After inlining ->
// let add = 2 + 2
// let i = add * 2

printfn "%i" i
// Output
// 8

パイプの前後

パイプ演算子は、パラメータを関数に単純かつエレガントな方法で渡すために使用されます。中間値を取り除き、関数呼び出しを読みやすくします。

F#には、2つのパイプ演算子があります。

  • 前方|> ):パラメータを左から右へ渡す

     let print message =
         printf "%s" message
     
     // "Hello World" will be passed as a parameter to the print function
     "Hello World" |> print
    
  • 後方<| ):パラメータを右から左へ渡す

     let print message =
         printf "%s" message
     
     // "Hello World" will be passed as a parameter to the print function
     print <| "Hello World"
    

パイプ演算子を使用しない例を次に示します。

// We want to create a sequence from 0 to 10 then:
// 1 Keep only even numbers
// 2 Multiply them by 2
// 3 Print the number

let mySeq = seq { 0..10 }
let filteredSeq = Seq.filter (fun c -> (c % 2) = 0) mySeq
let mappedSeq = Seq.map ((*) 2) filteredSeq
let finalSeq = Seq.map (sprintf "The value is %i.") mappedSeq

printfn "%A" finalSeq

前の例を短くして、前方パイプ演算子でよりきれいにすることができます:

// Using forward pipe, we can eliminate intermediate let binding
let finalSeq = 
    seq { 0..10 }
    |> Seq.filter (fun c -> (c % 2) = 0)
    |> Seq.map ((*) 2)
    |> Seq.map (sprintf "The value is %i.")

printfn "%A" finalSeq

各関数の結果は、パラメータとして次の関数に渡されます。

複数のパラメータをパイプ演算子に渡したい場合は、 |追加のパラメータごとに作成し、パラメータでタプルを作成します。ネイティブF#パイプ演算子は、最大3つのパラメータ(|||>または<|||)をサポートします。

let printPerson name age =
    printf "My name is %s, I'm %i years old" name age

("Foo", 20) ||> printPerson


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow