Композиция Haskell (.) Против оператора прямой передачи в F # (|>)

100

В F # |>довольно часто используется оператор конвейерной передачи ,. Однако в Haskell я видел только использование композиции функций (.). Я понимаю, что они связаны , но есть ли языковая причина, по которой конвейерная передача не используется в Haskell, или это что-то еще?

Бен Лингс
источник
2
В ответе lihanseys говорится, что &это Haskell's |>. Похоронен глубоко в этой теме, и мне потребовалось несколько дней, чтобы это обнаружить. Я часто его использую, потому что вы, естественно, читаете слева направо, чтобы следовать своему коду.
itmuckel

Ответы:

61

Я немного рассуждаю ...

Культура : я считаю, что |>это важный оператор в «культуре» F # и, возможно, аналогично .для Haskell. В F # есть оператор композиции функций, <<но я думаю, что сообщество F # склонно меньше использовать стиль без точек, чем сообщество Haskell.

Языковые различия : я недостаточно знаю оба языка для сравнения, но, возможно, правила обобщения let-привязок достаточно разные, чтобы повлиять на это. Например, я знаю, что в F # иногда пишу

let f = exp

не будет компилироваться, и вам потребуется явное eta-преобразование:

let f x = (exp) x   // or x |> exp

чтобы он компилировался. Это также уводит людей от бессмысленного / композиционного стиля в сторону конвейерного стиля. Кроме того, для вывода типа F # иногда требуется конвейерная обработка, так что известный тип отображается слева (см. Здесь ).

(Лично я считаю стиль без точек нечитаемым, но я полагаю, что каждая новая / другая вещь кажется нечитаемой, пока вы к ней не привыкнете.)

Я думаю, что оба они потенциально жизнеспособны на любом языке, и история / культура / случайность могут определить, почему каждое сообщество обосновалось на другом «аттракторе».

Брайан
источник
7
Я согласен с культурными различиями. Традиционный Haskell использует .и $, поэтому люди продолжают их использовать.
Amok
9
Без точек иногда лучше читать, чем с точками, иногда хуже. Я обычно использую его в качестве аргумента для таких функций, как map и filter, чтобы не загромождать лямбда. Иногда я использую его и в функциях верхнего уровня, но реже и только тогда, когда это что-то простое.
Пол Джонсон,
2
Я не вижу в этом особой культуры, в том смысле, что в отношении F # просто не так много выбора (по причинам, упомянутым вами и Ганешем). Так что я бы сказал, что оба варианта жизнеспособны в Haskell, но F # определенно лучше приспособлен для использования оператора конвейера.
Курт Шелфтхут, 01
2
да, культура чтения слева направо :) даже математике надо так учить ...
Николас
1
@nicolas слева направо - произвольный выбор. Вы можете привыкнуть справа налево.
Мартин Каподичи
86

В F # (|>)это важно из-за проверки типов слева направо. Например:

List.map (fun x -> x.Value) xs

обычно не выполняется проверка типов, потому что даже если тип xsизвестен, тип аргумента xлямбда неизвестен в то время, когда средство проверки типов его видит, поэтому он не знает, как разрешить x.Value.

Напротив

xs |> List.map (fun x -> x.Value)

будет работать нормально, потому что тип xsведет к известному типу x.

Проверка типов слева направо требуется из-за разрешения имен, задействованного в таких конструкциях, как x.Value. Саймон Пейтон Джонс написал предложение о добавлении подобного типа разрешения имен в Haskell, но вместо этого он предлагает использовать локальные ограничения, чтобы отслеживать, поддерживает ли тип конкретную операцию или нет. Таким образом, в первом примере требование, которому xтребуется Valueсвойство, будет перенесено до тех пор, пока не xsбудет обнаружено и это требование не будет разрешено. Однако это усложняет систему типов.

GS - Извинитесь перед Моникой
источник
1
Интересно то, что есть оператор (<|), аналогичный (.) В Haskell, с тем же направлением данных справа налево. Но как у него будет работать разрешение шрифтов?
The_Ghost,
7
(<|) на самом деле похож на ($) в Haskell. Проверка типов слева направо требуется только для разрешения таких вещей, как .Value, поэтому (<|) отлично работает в других сценариях или если вы используете явные аннотации типов.
GS - извиняюсь перед Моникой
44

Больше предположений, на этот раз со стороны преимущественно Haskell ...

($)- это переворот (|>), и его использование довольно распространено, когда вы не можете писать бессмысленный код. Итак, основная причина, по которой (|>)не используется Haskell, заключается в том, что его место уже занято ($).

Кроме того, исходя из небольшого опыта F #, я считаю, что (|>)код F # настолько популярен, потому что он напоминает Subject.Verb(Object)структуру OO. Поскольку F # нацелен на плавную функциональную / объектно-ориентированную интеграцию, Subject |> Verb Objectэто довольно плавный переход для новых функциональных программистов.

Лично мне тоже нравится думать слева направо, поэтому я использую (|>)в Haskell, но не думаю, что многие другие люди это делают.

Натан Шивели-Сандерс
источник
Привет, Натан, предопределено ли "flip ($)" где-нибудь на платформе Haskell? Имя "(|>)" уже определено в Data.Sequence с другим значением. Если еще не определено, как вы это называете? Я подумываю использовать «($>) = flip ($)»
mattbh
2
@mattbh: Не то, чтобы я мог найти с помощью Google. Я не знала Data.Sequence.|>, но $>выглядит разумным, чтобы избежать там конфликтов. Честно говоря, симпатичных операторов не так уж много, поэтому я бы просто использовал |>оба и управлял конфликтами в каждом конкретном случае. (Кроме того, я был бы соблазн просто псевдоним , Data.Sequence.|>как snoc)
Натан Шивели-Сандерс
2
($)и (|>)являются приложением, а не составом. Два связаны (как вопрос примечания) , но они не совпадают (ваше fcесть (Control.Arrow.>>>)для функций).
Натан Шивели-Сандерс,
11
На практике F # больше чем что-либо |>напоминает мне UNIX |.
Кевин Канту
3
Еще одно преимущество |>F # состоит в том, что он имеет хорошие свойства для IntelliSense в Visual Studio. Введите |>, и вы получите список функций, которые можно применить к значению слева, аналогично тому, что происходит при вводе текста .после объекта.
Кристофер Джонсон
32

Я думаю, мы что-то путаем. Haskell ( .) эквивалентен F # ( >>). Не путать с F # ( |>), которое представляет собой просто инвертированное приложение функции и похоже на Haskell ( $) - наоборот:

let (>>) f g x = g (f x)
let (|>) x f = f x

Я считаю, что программисты на Haskell $часто его используют . Возможно, не так часто, как привыкли использовать программисты на F # |>. С другой стороны, некоторые ребята из F # >>до смешного используют: http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx

ЭшлиФ
источник
2
как вы говорите, это похоже на $оператор Haskell - в обратном порядке, вы также можете легко определить его как: a |> b = flip ($)который становится эквивалентным конвейеру F #, например, вы можете сделать[1..10] |> map f
vis
8
Я думаю ( .) то же самое, что ( <<), тогда как ( >>) - обратная композиция. То есть ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3против( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
Добс Вандермер
-1 за «использовать >> в смехотворной степени». (Ну, я не знал, но вы поняли мою точку зрения). F в F # означает «функциональный», поэтому композиция функций допустима.
Florian F
1
Конечно, я согласен с тем, что композиция функций законна. Под "парнями из F #" я имею в виду себя! Это мой собственный блог. : P
AshleyF 01
Я не думаю, что .это эквивалентно >>. Я не знаю, есть ли у F #, <<но это будет эквивалент (как в Elm).
Мэтт Джойнер
28

Если вы хотите использовать F # |>в Haskell, тогда в Data.Function будет &оператор (с base 4.8.0.0).

Джегедус
источник
Любая причина для Haskell выбрать &более |>? Я чувствую, что |>это намного более интуитивно понятно, и это также напоминает мне оператор трубы Unix.
yeshengm
@yeshengm Я нахожу &очень интуитивным. Код почти правильно читается на английском, просто произносится &как «и». Например: 5 & factorial & showчитается вслух как «возьмите 5, затем возьмите факториал и примените к нему шоу».
Марсель Безиксдуз,
16

Композиция слева направо в Haskell

Некоторые люди также используют стиль слева направо (передача сообщений) в Haskell. См., Например, библиотеку mps на сайте Hackage. Пример:

euler_1 = ( [3,6..999] ++ [5,10..999] ).unique.sum

Я думаю, что в некоторых ситуациях этот стиль выглядит хорошо, но его сложнее читать (нужно знать библиотеку и все ее операторы, переопределение (.)тоже мешает).

В Control.Category , являющейся частью базового пакета, также есть операторы компоновки слева направо и справа налево . Сравните >>>и <<<соответственно:

ghci> :m + Control.Category
ghci> let f = (+2) ; g = (*3) in map ($1) [f >>> g, f <<< g]
[9,5]

Иногда есть веская причина предпочесть композицию слева направо: порядок оценки следует порядку чтения.

састанин
источник
Хорошо, так что (>>>) в значительной степени эквивалентно (|>)?
Ханзор
@ Ханзор Не совсем так. (|>) применяет аргумент, (>>>) в основном представляет собой композицию функций (или аналогичные вещи). Тогда, полагаю, есть разница в фиксированности (не проверял).
sastanin
15

Я видел, как >>>их использовали flip (.), и сам часто использую это, особенно для длинных цепочек, которые лучше всего понимать слева направо.

>>> на самом деле из Control.Arrow и работает не только с функциями.

призрак
источник
>>>определяется в Control.Category.
Стивен Шоу
13

Помимо стиля и культуры, это сводится к оптимизации дизайна языка для чистого или нечистого кода.

|>Оператор часто встречается в F # в основном потому , что это помогает скрыть два ограничения , которые появляются с преимущественно-нечистым кодом:

  • Выведение типов слева направо без структурных подтипов.
  • Ограничение стоимости.

Обратите внимание, что первое ограничение не существует в OCaml, потому что подтип является структурным, а не номинальным, поэтому структурный тип легко уточняется с помощью унификации по мере продвижения вывода типа.

Haskell идет на другой компромисс, предпочитая сосредоточиться на преимущественно чистом коде, где эти ограничения могут быть сняты.

JD
источник
8

Я думаю, что оператор F # pipe forward ( |>) должен vs ( & ) в haskell.

// pipe operator example in haskell

factorial :: (Eq a, Num a) =>  a -> a
factorial x =
  case x of
    1 -> 1
    _ -> x * factorial (x-1)
// terminal
ghic >> 5 & factorial & show

Если вам не нравится &оператор ( ), вы можете настроить его как F # или Elixir:

(|>) :: a -> (a -> b) -> b
(|>) x f = f x
infixl 1 |>
ghci>> 5 |> factorial |> show

Почему infixl 1 |>? См. Документ в Data-Function (&)

infixl = infix + левая ассоциативность

infixr = infix + правая ассоциативность


(.)

( .) означает композицию функций. Это означает (fg) (x) = f (g (x)) в Math.

foo = negate . (*3)
// ouput -3
ghci>> foo 1
// ouput -15
ghci>> foo 5

это равно

// (1)
foo x = negate (x * 3) 

или

// (2)
foo x = negate $ x * 3 

( $) также определяется в Data-Function ($) .

( .) используется для создания Hight Order Functionили closure in js. См. Пример:


// (1) use lamda expression to create a Hight Order Function
ghci> map (\x -> negate (abs x)) [5,-3,-6,7,-3,2,-19,24]  
[-5,-3,-6,-7,-3,-2,-19,-24]


// (2) use . operator to create a Hight Order Function
ghci> map (negate . abs) [5,-3,-6,7,-3,2,-19,24]  
[-5,-3,-6,-7,-3,-2,-19,-24]

Вау, меньше (код) лучше.


Сравните |>и.

ghci> 5 |> factorial |> show

// equals

ghci> (show . factorial) 5 

// equals

ghci> show . factorial $ 5 

Это разница между left —> rightи right —> left. ⊙﹏⊙ |||

Гуманизация

|>и &лучше чем.

так как

ghci> sum (replicate 5 (max 6.7 8.9))

// equals

ghci> 8.9 & max 6.7 & replicate 5 & sum

// equals

ghci> 8.9 |> max 6.7 |> replicate 5 |> sum

// equals

ghci> (sum . replicate 5 . max 6.7) 8.9

// equals

ghci> sum . replicate 5 . max 6.7 $ 8.9

Как функциональное программирование на объектно-ориентированном языке?

посетите http://reactivex.io/

ИТ поддержка :

  • Java: RxJava
  • JavaScript: RxJS
  • C #: Rx.NET
  • C # (Единство): UniRx
  • Scala: RxScala
  • Clojure: RxClojure
  • C ++: RxCpp
  • Lua: RxLua
  • Рубин: Rx.rb
  • Python: RxPY
  • Перейти: RxGo
  • Groovy: RxGroovy
  • JRuby: RxJRuby
  • Котлин: RxKotlin
  • Swift: RxSwift
  • PHP: RxPHP
  • Эликсир: реактивный
  • Дротик: RxDart
Лиханси
источник
1

Это мой первый день, когда я пробую Haskell (после Rust и F #), и я смог определить оператор F # |>:

(|>) :: a -> (a -> b) -> b
(|>) x f = f x
infixl 0 |>

и вроде работает:

factorial x =
  case x of
    1 -> 1
    _ -> x * factorial (x-1)

main =     
    5 |> factorial |> print

Бьюсь об заклад, эксперт по Haskell может предложить вам еще лучшее решение.

тиб
источник
1
вы также можете определять инфиксные операторы infix :) x |> f = f x
Джейми