Поиск…


Функции более одного параметра

В 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


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow