Преобразование операторов трубопровода 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 #.