Если вы пытались заняться функциональным программированием, скорее всего, вы слышали о композиции и конвейерной обработке. Эти концепции применимы к бесточечному программированию. Это попытка простого и ясного объяснения композиции функций и конвейеров. Я напишу эти примеры на 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.