Composição de Haskell (.) Vs operador de pipe forward de F # (|>)

100

Em F #, o uso do operador pipe-forward,, |>é bastante comum. No entanto, em Haskell, eu só vi composição de funções,, (.)sendo usada. Eu entendo que eles estão relacionados , mas há um motivo de linguagem para que o pipe-forward não seja usado em Haskell ou é outra coisa?

Ben Lings
fonte
2
A resposta de Lihanseys afirma que &é de Haskell |>. Enterrado profundamente neste tópico e levei alguns dias para descobrir. Eu uso muito, porque você naturalmente lê da esquerda para a direita para seguir o seu código.
itmuckel

Respostas:

61

Estou sendo um pouco especulativo ...

Cultura : Eu acho que |>é um operador importante na "cultura" F #, e talvez da mesma forma .para Haskell. F # tem um operador de composição de função, <<mas acho que a comunidade F # tende a usar menos o estilo sem pontos do que a comunidade Haskell.

Diferenças de idioma : Não sei o suficiente sobre os dois idiomas para comparar, mas talvez as regras para generalizar as ligações let sejam suficientemente diferentes para afetar isso. Por exemplo, eu sei em F # às vezes escrevendo

let f = exp

não irá compilar e você precisa de conversão eta explícita:

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

para fazê-lo compilar. Isso também afasta as pessoas do estilo de composição / sem pontos e em direção ao estilo de tubulação. Além disso, a inferência de tipo F # às vezes exige pipelining, para que um tipo conhecido apareça à esquerda (veja aqui ).

(Pessoalmente, acho o estilo sem pontos ilegível, mas suponho que cada coisa nova / diferente parece ilegível até que você se acostume a ela.)

Eu acho que ambos são potencialmente viáveis ​​em qualquer idioma, e história / cultura / acidente podem definir porque cada comunidade se estabeleceu em um "atrator" diferente.

Brian
fonte
7
Eu concordo com as diferenças culturais. Haskell tradicional faz uso de .e $, então as pessoas continuam a usá-los.
Amok
9
Ponto livre às vezes é mais legível do que pontual, às vezes menos. Eu geralmente o uso no argumento de funções como map e filter, para evitar que um lambda atrapalhe as coisas. Às vezes também o uso em funções de nível superior, mas com menos frequência e apenas quando é algo simples.
Paul Johnson,
2
Não vejo muita cultura nisso, no sentido de que simplesmente não há muita escolha no assunto no que diz respeito ao F # (pelos motivos que você e Ganesh mencionam). Então, eu diria que ambos são viáveis ​​em Haskell, mas F # é definitivamente muito melhor equipado para usar o operador de duto.
Kurt Schelfthout
2
sim, a cultura da leitura da esquerda para a direita :) até matemática deveria ser ensinada dessa forma ...
nicolas
1
@nicolas da esquerda para a direita é uma escolha arbitrária. Você pode se acostumar com a direita para a esquerda.
Martin Capodici
86

Em F # (|>)é importante por causa da verificação de tipo da esquerda para a direita. Por exemplo:

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

geralmente não verifica o tipo, porque mesmo se o tipo de xsfor conhecido, o tipo do argumento xpara o lambda não é conhecido no momento em que o verificador de tipo o vê, então ele não sabe como resolver x.Value.

Em contraste

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

vai funcionar bem, porque o tipo de xsvai levar ao tipo de xser conhecido.

A verificação de tipo da esquerda para a direita é necessária devido à resolução de nomes envolvida em construções como x.Value. Simon Peyton Jones escreveu uma proposta para adicionar um tipo semelhante de resolução de nome a Haskell, mas ele sugere o uso de restrições locais para rastrear se um tipo suporta uma operação específica ou não. Portanto, na primeira amostra, o requisito que xprecisa de uma Valuepropriedade seria transportado até xsser visto e esse requisito pudesse ser resolvido. Porém, isso complica o sistema de tipos.

GS - Peça desculpas a Monica
fonte
1
O interessante é que existe um operador (<|) semelhante a (.) Em Haskell com a mesma direção dos dados da direita para a esquerda. Mas como funcionará a resolução de tipo para isso?
The_Ghost
7
(<|) é realmente semelhante ao de Haskell ($). A verificação de tipo da esquerda para a direita só é necessária para resolver coisas como .Value, portanto (<|) funciona bem em outros cenários ou se você usar anotações de tipo explícitas.
GS - Peça desculpas a Monica
44

Mais especulação, desta vez do lado predominantemente de Haskell ...

($)é a inversão de (|>), e seu uso é bastante comum quando você não pode escrever código sem pontos. Portanto, o principal motivo que (|>)não é usado em Haskell é que seu lugar já está ocupado ($).

Além disso, falando com um pouco de experiência em F #, acho que (|>)é tão popular no código F # porque se assemelha à Subject.Verb(Object)estrutura do OO. Uma vez que o F # visa uma integração funcional / OO suave, Subject |> Verb Objecté uma transição bastante suave para novos programadores funcionais.

Pessoalmente, também gosto de pensar da esquerda para a direita, então uso (|>)em Haskell, mas não acho que muitas outras pessoas o façam.

Nathan Shively-Sanders
fonte
Olá, Nathan, "flip ($)" está predefinido em algum lugar da plataforma Haskell? O nome "(|>)" já está definido em Data.Sequence com outro significado. Se ainda não foi definido, como você o chama? Estou pensando em
usar
2
@mattbh: Não que eu consiga encontrar no Hoogle. Eu não sabia Data.Sequence.|>, mas $>parece razoável evitar conflitos aí. Honestamente, existem apenas alguns operadores bonitos, então eu usaria apenas |>para ambos e gerenciaria os conflitos caso a caso. (Também eu estaria tentado a apenas apelido Data.Sequence.|>como snoc)
Nathan Shively-Sanders
2
($)e (|>)são aplicação, não composição. Os dois estão relacionados (como as notas da pergunta), mas eles não são o mesmo (seu fcé (Control.Arrow.>>>)para funções).
Nathan Shively-Sanders
11
Na prática, o F # |>realmente me lembra do UNIX |mais do que qualquer outra coisa.
Kevin Cantu,
3
Outro benefício do |>F # é que ele tem boas propriedades para IntelliSense no Visual Studio. Digite |>e você obterá uma lista de funções que podem ser aplicadas ao valor à esquerda, semelhante ao que acontece ao digitar .depois de um objeto.
Kristopher Johnson
32

Acho que estamos confundindo as coisas. Haskell's ( .) é equivalente a F #'s ( >>). Não deve ser confundido com F #'s ( |>), que é apenas uma aplicação de função invertida e é como Haskell ( $) - invertido:

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

Eu acredito que os programadores de Haskell usam $frequentemente. Talvez não com a frequência que os programadores de F # costumam usar |>. Por outro lado, alguns caras do F # usam >>até um grau ridículo: http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx

AshleyF
fonte
2
como você diz, é como o $operador de Haskell - invertido, você também pode defini-lo facilmente como: o a |> b = flip ($)que se torna equivalente ao pipeline do F #, por exemplo, você pode fazer[1..10] |> map f
vis
8
Acho ( .) o mesmo que ( <<), enquanto ( >>) é a composição reversa. Isso é ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3vs( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
Dobes Vandermeer
-1 para "usar >> até um grau ridículo". (Bem, eu não fiz, mas você entendeu). F em F # é para "funcional", então a composição da função é legítima.
Florian F
1
Certamente, concordo que a composição de funções é legítima. Por "alguns caras do F #" estou me referindo a mim mesmo! Esse é meu próprio blog. : P
AshleyF 01 de
Eu não acho que .seja equivalente a >>. Não sei se F # tem, <<mas isso seria o equivalente (como no Elm).
Matt Joiner de
28

Se você quiser usar o F # |>em Haskell, em Data.Function é o &operador (desde base 4.8.0.0).

Jhegedus
fonte
Alguma razão para Haskell escolher o &lugar |>? Eu sinto que |>é muito mais intuitivo, e também me lembra do operador de pipe Unix.
yeshengm
16

Composição da esquerda para a direita em Haskell

Algumas pessoas também usam o estilo da esquerda para a direita (passagem de mensagens) em Haskell. Veja, por exemplo, biblioteca mps no Hackage. Um exemplo:

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

Acho que este estilo fica bem em algumas situações, mas é mais difícil de ler (é preciso conhecer a biblioteca e todos os seus operadores, a redefinição (.)também atrapalha).

Existem também operadores de composição da esquerda para a direita e da direita para a esquerda em Control.Category , parte do pacote básico. Compare >>>e <<<respectivamente:

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

Há uma boa razão para preferir a composição da esquerda para a direita às vezes: a ordem de avaliação segue a ordem de leitura.

sastanina
fonte
Legal, então (>>>) é amplamente equiparável a (|>)?
Khanzor de
@Khanzor Não exatamente. (|>) aplica um argumento, (>>>) é principalmente composição de função (ou coisas semelhantes). Então suponho que haja alguma diferença de fixidez (não verifiquei).
sastanin
15

Já vi >>>ser usado para isso flip (.), e frequentemente uso isso, especialmente para cadeias longas que são mais bem compreendidas da esquerda para a direita.

>>> é, na verdade, de Control.Arrow e funciona em mais do que apenas funções.

spookylukey
fonte
>>>é definido em Control.Category.
Steven Shaw
13

Além do estilo e da cultura, isso se resume a otimizar o design da linguagem para código puro ou impuro.

O |>operador é comum em F # principalmente porque ajuda a ocultar duas limitações que aparecem com o código predominantemente impuro:

  • Inferência de tipo da esquerda para a direita sem subtipos estruturais.
  • A restrição de valor.

Observe que a primeira limitação não existe em OCaml porque a subtipagem é estrutural em vez de nominal, de modo que o tipo estrutural é facilmente refinado por meio da unificação conforme a inferência de tipo progride.

Haskell faz uma troca diferente, optando por focar no código predominantemente puro, onde essas limitações podem ser removidas.

JD
fonte
8

Acho que o operador de pipe forward do F # ( |>) deve vs ( & ) em 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

Se você não gosta do &operador ( ), pode personalizá-lo como F # ou Elixir:

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

Porque infixl 1 |>? Veja o documento em Data-Function (&)

infixl = infix + associatividade à esquerda

infixr = infix + associatividade direita


(.)

( .) significa composição da função. Significa (fg) (x) = f (g (x)) em matemática.

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

é igual a

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

ou

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

O $operador ( ) também é definido em Data-Function ($) .

( .) é usado para criar Hight Order Functionou closure in js. Consultar exemplo:


// (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]

Uau, menos (código) é melhor.


Compare |>e.

ghci> 5 |> factorial |> show

// equals

ghci> (show . factorial) 5 

// equals

ghci> show . factorial $ 5 

É a diferença entre left —> righte right —> left. ⊙﹏⊙ |||

Humanização

|>e &é melhor que.

Porque

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

Como fazer programação funcional em linguagem orientada a objetos?

visite http://reactivex.io/

Suporta:

  • Java: RxJava
  • JavaScript: RxJS
  • C #: Rx.NET
  • C # (unidade): UniRx
  • Scala: RxScala
  • Clojure: RxClojure
  • C ++: RxCpp
  • Lua: RxLua
  • Ruby: Rx.rb
  • Python: RxPY
  • Go: RxGo
  • Groovy: RxGroovy
  • JRuby: RxJRuby
  • Kotlin: RxKotlin
  • Swift: RxSwift
  • PHP: RxPHP
  • Elixir: reaxivo
  • Dart: RxDart
Lihansey
fonte
1

Este é meu primeiro dia para experimentar Haskell (depois de Rust e F #), e pude definir o operador F # |>:

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

E parece que funciona:

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

main =     
    5 |> factorial |> print

Aposto que um especialista em Haskell pode lhe dar uma solução ainda melhor.

tib
fonte
1
você também pode definir operadores infixo infixo :) x |> f = f x
Jamie