Если вы пытались заняться функциональным программированием, скорее всего, вы слышали о композиции и конвейерной обработке. Эти концепции применимы к бесточечному программированию. Это попытка простого и ясного объяснения композиции функций и конвейеров. Я напишу эти примеры на F#, но концепции применимы и к другим языкам, таким как Haskell. Они просто иногда используют разных операторов.

Предварительные требования: базовые знания в области программирования. Это объяснение будет простым.

Конвейер
Давайте начнем с более простого из двух (по крайней мере, согласно моему опыту): конвейера. Есть два способа передать значение функции. Вперед и назад. Когда вы выполняете прямую передачу, вы применяете левую сторону прямой трубы к самому левому аргументу правой стороны трубы. В F# передача по конвейеру выполняется с помощью оператора |>. Это значит, что

"Hello World!" |> printfn "%s"
// is the same as
printfn "%s" "Hello World!"
// Another example would be
let add1 x = x + 1
5 |> add1
// is the same as
add1 5
// And
let add x y = x + y
5 |> (1 |> add) // 6
// Parentheses are used to clarify precende (order)

Он просто применяет something к первому незаполненному аргументу (если читать слева направо). В Haskell для этого используется оператор is&. Теперь вам может быть понятен обратный ход. То же самое, но читается справа налево. В F# обратные конвейеры выполняются с помощью оператора <|. Например,

let subtract x y = x - y
subtract <| 5 <| 1 // 4
subtract 5 1 // 4

В Haskell это делается с помощью $.

Композиция
Композиция функций намного проще, чем может показаться. Это создание функции, которая применяет функцию к результату другой. В F# композиция выполняется с помощью >> и <<. Как и конвейер, >> читается слева направо, а << читается справа налево.

let add1 x = x + 1
let multiplyBy5 x = x * 5
let addThenMultiply x = (add1 >> multiplyBy5) x
printfn "%d" (addThenMultiply 4) // 25
printfn "%d" (multiplyBy5 (add1 4)) // 25

В Haskell . используется для композиции функций.

Зачем?
У вас может возникнуть справедливый вопрос: зачем связывать и создавать функции? Ну, конвейер и композиция помогают нам очистить наш код от явных правил для скобок. Рассмотрим пример для композиции. Первый читается легче. А если мы будем использовать его повторно, то сэкономим на написании лишнего кода. Это упрощает задачу. А как быть с трубопроводом? Трубопровод имеет те же приложения. Вы бы предпочли Seq.toList numbers |> Seq.iter (printfn "%d") или Seq.iter (printfn "%d" (Seq.toList numbers))? Возможно, вы бы предпочли последнее. Но иногда более идиоматично использовать композицию и конвейер. А иногда есть функция с длинным списком аргументов, которые нужно указать, с длинными именами. Вы бы предпочли

AVeryLongFunction VeryLongArgument1 VeryLongArgument2 (x VeryLongArgument3AppliedToX) (VeryLongFunctionName VeryLongArgument4AppliedToVeryLongFunctionApplied)
// This is in one line, by the way.

or

VeryLongArgument1 
|> VeryLongArgument2 
|> (X VeryLongArgument3AppliedToX) 
|> (VeryLongFunctionName VeryLongArgument4AppliedToVeryLongFunctionName)
|> AVeryLongFunction
// The fourth argument is until the pipe.

Бывают случаи, когда это действительно происходит. Просто посмотрите на несколько примеров использования Avalonia.FuncUI. Там нас спасают операторы трубопроводов.

Первоначально опубликовано на http://github.com.