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

Другие вопросы по тегам