Преобразование операторов конвейера F# (<|, >>, <<) в OCaml
Я конвертирую код F# в OCaml и вижу много применений этого оператора конвейера. <|
, например:
let printPeg expr =
printfn "%s" <| pegToString expr
<|
Оператор, по-видимому, определяется как просто:
# let ( <| ) a b = a b ;;
val ( <| ) : ('a -> 'b) -> 'a -> 'b = <fun>
Мне интересно, почему они удосужились определить и использовать этот оператор в F#, просто чтобы они не могли ставить такие символы:
let printPeg expr =
Printf.printf "%s" ( pegToString expr )
Насколько я могу сказать, это будет преобразование кода F# выше в OCaml, правильно?
Кроме того, как бы я реализовал F# <<
а также >>
операторы в Ocaml?
(|>
Оператор, кажется, просто: let ( |> ) a b = b a ;;
)
4 ответа
Непосредственно из источника F#:
let inline (|>) x f = f x
let inline (||>) (x1,x2) f = f x1 x2
let inline (|||>) (x1,x2,x3) f = f x1 x2 x3
let inline (<|) f x = f x
let inline (<||) f (x1,x2) = f x1 x2
let inline (<|||) f (x1,x2,x3) = f x1 x2 x3
let inline (>>) f g x = g(f x)
let inline (<<) f g x = f(g x)
почему они пытаются определить и использовать этот оператор в F#, просто так они могут избежать использования паренов?
Это потому, что функциональный способ программирования предполагает передачу значения через цепочку функций. Для сравнения:
let f1 str server =
str
|> parseUserName
|> getUserByName server
|> validateLogin <| DateTime.Now
let f2 str server =
validateLogin(getUserByName(server, (parseUserName str)), DateTime.Now)
В первом фрагменте мы ясно видим все, что происходит со значением. Читая второй, мы должны пройти через все парены, чтобы выяснить, что происходит.
Эта статья о композиции функций представляется актуальной.
Так что да, в обычной жизни речь идет в основном о паренсе. Кроме того, операторы конвейера тесно связаны с частичным применением функций и бессмысленным стилем кодирования. См. " Программирование бессмысленно", например.
Трубопровод |>
и функция состав >>
<<
операторы могут производить еще один интересный эффект, когда они передаются функциям более высокого уровня, как здесь.
OCaml Batteries поддерживает эти операторы, но по причинам старшинства, ассоциативности и других синтаксических особенностей (например, Camlp4) он использует разные символы. Какие конкретные символы для использования были только что установлены, есть некоторые изменения. Смотрите: Батареи API:
val (|>) : 'a -> ('a -> 'b) -> 'b
Функция приложения. x |> f эквивалентно f x.
val ( **> ) : ('a -> 'b) -> 'a -> 'b
Функция приложения. f **> x эквивалентно f x. Примечание Имя этого оператора не написано на камне. Это должно скоро измениться.
val (|-) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
Состав функции. f |- g это весело x -> g (f x). Это также эквивалентно применению <** дважды.
val (-|) : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b
Состав функции. f -| g это весело x -> f (g x). Математически это оператор o.
Но аккумулятор багажника обеспечивает:
val ( @@ ) : ('a -> 'b) -> 'a -> 'b
Функция приложения. [f @@ x] эквивалентно [f x].
val ( % ) : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b
Состав функции: математический оператор [o].
val ( |> ) : 'a -> ('a -> 'b) -> 'b
"Труба": функция приложения. [x |> f] эквивалентно [f x].
val ( %> ) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
Состав трубопровода. [f %> g] - это [fun x -> g (f x)].
Мне интересно, почему они удосужились определить и использовать этот оператор в F#, просто чтобы они могли избежать использования таких символов?
Отличный вопрос. Конкретный оператор, на который вы ссылаетесь (<|
) довольно бесполезный IME. Он позволяет вам избегать скобок в редких случаях, но в целом он усложняет синтаксис, перетаскивая больше операторов, и затрудняет понимание вашего кода менее опытными программистами F# (которых сейчас много). Так что я перестал его использовать.
|>
Оператор гораздо более полезен, но только потому, что помогает F# правильно выводить типы в ситуациях, когда у OCaml не будет проблем. Например, вот какой-то OCaml:
List.map (fun o -> o#foo) os
Прямой эквивалент терпит неудачу в F#, потому что тип o
не может быть выведено до прочтения его foo
свойство, так что идиоматическое решение состоит в том, чтобы переписать код, как это, используя |>
поэтому F# может вывести тип o
до foo
используется:
os |> List.map (fun o -> o.foo)
Я редко пользуюсь другими операторами (<<
а также >>
) потому что они также усложняют синтаксис. Мне также не нравятся библиотеки комбинаторов синтаксического анализатора, которые тянут много операторов.
Байтбастер привел интересный пример:
let f1 str server =
str
|> parseUserName
|> getUserByName server
|> validateLogin <| DateTime.Now
Я бы написал это так:
let f2 str server =
let userName = parseUserName str
let user = getUserByName server userName
validateLogin user DateTime.Now
В моем коде нет скобок. У моих временных файлов есть имена, поэтому они появляются в отладчике, и я могу их проверить, а Intellisense может дать мне возврат типа, когда я наведу на них указатель мыши. Эти характеристики полезны для производственного кода, который будут поддерживать неопытные программисты F#.