Преобразование операторов трубопровода 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 ;;
)
Ответы
Ответ 1
Непосредственно из источника 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)
Ответ 2
почему они беспокоятся о том, чтобы определить и использовать этот оператор в F #, так ли это, чтобы они не могли помещать в parens?
Это потому, что функциональный способ программирования предполагает нарезку значения через цепочку функций. Для сравнения:
let f1 str server =
str
|> parseUserName
|> getUserByName server
|> validateLogin <| DateTime.Now
let f2 str server =
validateLogin(getUserByName(server, (parseUserName str)), DateTime.Now)
В первом фрагменте мы четко видим все, что происходит со значением. Читая второй, мы должны пройти через все парны, чтобы выяснить, что происходит.
Эта статья о составе функции кажется актуальной.
Так что да, в обычной жизни, это в основном о паранах. Но также, операторы трубопроводов тесно связаны с приложением частичной функции и типом кодирования без точек. См. Программирование "Бессмысленно" , например.
Операторы конвейера |>
и функции >>
<<
могут создавать еще один интересный эффект, когда они передаются более высокоуровневым функциям, например .
Ответ 3
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
Приложение "pipe": function. [x | > f] эквивалентно [f x].
val ( %> ) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
Состав функции трубопровода. [f% > g] [fun x → g (f x)].
Ответ 4
Мне интересно, почему они не хотят определять и использовать этот оператор в 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)
Я редко использую другие операторы (<<
и >>
), потому что они также усложняют синтаксис. Мне также не нравятся библиотеки парсерных комбинаторов, которые тянут множество операторов.
Приведенный пример Bytebuster интересен:
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 #.