Поиск…
Функции более одного параметра
В F # все функции принимают ровно один параметр . Это кажется нечетным утверждением, поскольку тривиально легко объявить более одного параметра в объявлении функции:
let add x y = x + y
Но если вы введете это объявление функции в интерактивный интерпретатор F #, вы увидите, что его подпись типа:
val add : x:int -> y:int -> int
Без имен параметров эта подпись является int -> int -> int
. Оператор ->
является право-ассоциативным, что означает, что эта сигнатура эквивалентна int -> (int -> int)
. Другими словами, add
- это функция, которая принимает один параметр int
и возвращает функцию, которая принимает один int
и возвращает int
. Попытайся:
let addTwo = add 2
// val addTwo : (int -> int)
let five = addTwo 3
// val five : int = 5
Тем не менее, вы также можете вызвать функцию, например, add
более «обычную» манеру, прямо передав ей два параметра, и она будет работать так, как вы ожидали:
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
Этот метод мышления о многопараметрических функциях как функции, которые принимают один параметр и возвращают новые функции (которые, в свою очередь, могут принимать один параметр и возвращать новые функции, пока вы не достигнете конечной функции, которая принимает окончательный параметр и, наконец, возвращает результат) называется currying , в честь математика Haskell Curry, который славится разработкой концепции. (Он был изобретен кем-то другим, но Карри заслуженно получает большую часть кредита.)
Эта концепция используется во всем 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
.
Но ждать! Как F # знал, что x
является целым параметром, так как вы никогда не указали его тип? Это связано с типом вывода . Поскольку в теле функции вы умножаете x
на 2
, типы x
и 2
должны быть одинаковыми. (Как правило, F # не будет неявно передавать значения для разных типов, вы должны явно указывать любые типы приемов).
Если вы хотите создать функцию, которая не принимает никаких параметров, это неправильный способ сделать это:
let hello = // This is a value, not a function
printfn "Hello world"
Правильный способ сделать это:
let hello () =
printfn "Hello world"
Эта функция hello
имеет unit -> unit
, который объясняется в типе «unit» .
Curried vs Tupled Functions
Существует два способа определения функций с несколькими параметрами в функциях F #, Curried и Tupled.
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 framework), используются в F # с формой с расширением. Большинство функций в базовых модулях F # используются с формой Curried.
Форма Curried считается идиоматической F #, поскольку она допускает частичное применение. Ни один из следующих двух примеров невозможен с помощью формы «Тупик».
let addTen = curriedAdd 10 // Signature: int -> int
// Or with the Piping operator
3 |> curriedAdd 7 // Evaluates to 10
Причина этого в том, что функция Curried при вызове с одним параметром возвращает функцию. Добро пожаловать в функциональное программирование!
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 # существуют два оператора:
Вперед (
|>
): передача параметров слева направо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 # поддерживает до трех параметров (|||> или <|||).
let printPerson name age =
printf "My name is %s, I'm %i years old" name age
("Foo", 20) ||> printPerson