Composition Haskell (.) Vs opérateur pipe forward de F # (|>)


100

En F #, l'utilisation de l'opérateur pipe-forward,, |>est assez courante. Cependant, dans Haskell, je n'ai jamais vu que des compositions de fonctions (.), utilisées. Je comprends qu'ils sont liés , mais y a-t-il une raison linguistique pour laquelle le pipe-forward n'est pas utilisé dans Haskell ou est-ce autre chose?


2
La réponse de lihanseys déclare que &c'est celle de Haskell |>. Enterré profondément dans ce fil et m'a pris quelques jours à découvrir. Je l'utilise beaucoup, car vous lisez naturellement de gauche à droite pour suivre votre code.
itmuckel

Réponses:


61

Je suis un peu spéculatif ...

Culture : Je pense que |>c'est un opérateur important dans la "culture" F #, et peut-être de la même manière .pour Haskell. F # a un opérateur de composition de fonctions <<mais je pense que la communauté F # a moins tendance à utiliser le style sans points que la communauté Haskell.

Différences linguistiques : je ne connais pas assez les deux langues pour comparer, mais peut-être que les règles de généralisation des liaisons let sont suffisamment différentes pour affecter cela. Par exemple, je sais en F # parfois en écrivant

let f = exp

ne compilera pas, et vous avez besoin d'une conversion eta explicite:

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

pour le faire compiler. Cela éloigne également les gens du style sans points / compositionnel et vers le style de pipelining. De plus, l'inférence de type F # nécessite parfois un pipelining, de sorte qu'un type connu apparaît sur la gauche (voir ici ).

(Personnellement, je trouve le style sans points illisible, mais je suppose que chaque chose nouvelle / différente semble illisible jusqu'à ce que vous vous y habituiez.)

Je pense que les deux sont potentiellement viables dans l'une ou l'autre langue, et l'histoire / la culture / l'accident peuvent définir pourquoi chaque communauté s'est installée à un «attracteur» différent.


7
Je suis d'accord avec les différences culturelles. Haskell traditionnel utilise .et $, donc les gens continuent de les utiliser.
Amok

9
Point free est parfois plus lisible que pointu, parfois moins. Je l'utilise généralement dans l'argument des fonctions comme la carte et le filtre, pour éviter d'avoir un lambda encombrant les choses. Je l'utilise parfois aussi dans des fonctions de haut niveau, mais moins souvent et uniquement lorsque c'est quelque chose de simple.
Paul Johnson

2
Je n'y vois pas beaucoup de culture, en ce sens qu'il n'y a tout simplement pas beaucoup de choix en la matière en ce qui concerne F # (pour les raisons que vous et Ganesh mentionnez). Je dirais donc que les deux sont viables à Haskell, mais F # est certainement beaucoup mieux équipé pour utiliser l'opérateur de pipeline.
Kurt Schelfthout

2
oui, la culture de la lecture de gauche à droite :) même les mathématiques devraient être enseignées de cette façon ...
nicolas

1
@nicolas de gauche à droite est un choix arbitraire. Vous pouvez vous habituer de droite à gauche.
Martin Capodici

86

En F # (|>)est important en raison de la vérification de type de gauche à droite. Par exemple:

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

généralement pas de vérification de type, car même si le type de xsest connu, le type de l'argument xdu lambda n'est pas connu au moment où le vérificateur de type le voit, donc il ne sait pas comment le résoudre x.Value.

En revanche

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

fonctionnera très bien, car le type de xsmènera au type d' xêtre connu.

La vérification de type de gauche à droite est requise en raison de la résolution de nom impliquée dans des constructions comme x.Value. Simon Peyton Jones a écrit une proposition pour ajouter un type similaire de résolution de noms à Haskell, mais il suggère d'utiliser des contraintes locales pour savoir si un type prend en charge une opération particulière ou non. Ainsi, dans le premier échantillon, l'exigence qui xnécessite une Valuepropriété serait reportée jusqu'à ce qu'elle xssoit vue et cette exigence pourrait être résolue. Cela complique cependant le système de types.


1
Il est intéressant de noter qu'il existe un opérateur (<|) similaire à (.) Dans Haskell avec la même direction des données de droite à gauche. Mais comment fonctionnera la résolution de type pour cela?
The_Ghost

7
(<|) est en fait similaire à ($) de Haskell. La vérification de type de gauche à droite n'est requise que pour résoudre des choses comme .Value, donc (<|) fonctionne très bien dans d'autres scénarios, ou si vous utilisez des annotations de type explicite.
GS - Présentez vos excuses à Monica

44

Plus de spéculations, cette fois du côté majoritairement Haskell ...

($)est le flip de (|>), et son utilisation est assez courante lorsque vous ne pouvez pas écrire de code sans point. Donc, la principale raison qui (|>)n'est pas utilisée dans Haskell est que sa place est déjà prise par($) .

De plus, en parlant d'un peu d'expérience F #, je pense que (|>)c'est si populaire dans le code F # parce qu'il ressemble à la Subject.Verb(Object)structure d'OO. Puisque F # vise une intégration fonctionnelle / OO fluide, Subject |> Verb Objectc'est une transition assez fluide pour les nouveaux programmeurs fonctionnels.

Personnellement, j'aime aussi penser de gauche à droite, donc j'utilise (|>)dans Haskell, mais je ne pense pas que beaucoup d'autres le font.


Salut Nathan, est-ce que "flip ($)" est prédéfini n'importe où dans la plateforme Haskell? Le nom "(|>)" est déjà défini dans Data.Sequence avec une autre signification. Si ce n'est pas déjà défini, comment l'appelez-vous? Je pense aller avec "($>) = flip ($)"
mattbh

2
@mattbh: Pas que je puisse trouver avec Hoogle. Je ne savais pas Data.Sequence.|>, mais $>semble raisonnable pour éviter les conflits là-bas. Honnêtement, il n'y a qu'un nombre limité d'opérateurs attrayants, donc je voudrais simplement utiliser |>pour les deux et gérer les conflits au cas par cas. (Aussi je serais tenté de juste alias Data.Sequence.|>comme snoc)
Nathan Shively-Sanders

2
($)et (|>)sont l'application non la composition. Les deux sont liés (comme le note la question) mais ils ne sont pas identiques (votre fcest (Control.Arrow.>>>)pour les fonctions).
Nathan Shively-Sanders

11
En pratique, les F # |>me rappellent en fait UNIX |plus qu'autre chose.
Kevin Cantu

3
Un autre avantage de |>F # est qu'il possède de bonnes propriétés pour IntelliSense dans Visual Studio. Tapez |>, et vous obtenez une liste de fonctions qui peuvent être appliquées à la valeur de gauche, similaire à ce qui se passe lors de la saisie .après un objet.
Kristopher Johnson

32

Je pense que nous confondons les choses. Haskell's ( .) est équivalent à F # 's ( >>). À ne pas confondre avec F # 's ( |>) qui est juste une application de fonction inversée et est comme Haskell ( $) - inversé:

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

Je pense que les programmeurs Haskell utilisent $souvent. Peut-être pas aussi souvent que les programmeurs F # ont tendance à utiliser |>. D'un autre côté, certains types F # utilisent >>à un degré ridicule: http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx


2
comme vous dites que c'est comme l' $opérateur de Haskell - inversé, vous pouvez aussi facilement le définir comme: a |> b = flip ($)qui devient équivalent au pipeline de F #, par exemple vous pouvez le faire[1..10] |> map f
vis

8
Je pense que ( .) est la même chose que ( <<), alors que ( >>) est la composition inverse. C'est ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3vs( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
Dobes Vandermeer

-1 pour "utiliser >> à un degré ridicule". (Eh bien, je ne l'ai pas fait mais vous avez compris mon point). F dans F # est pour "fonctionnel", donc la composition de fonction est légitime.
Florian F

1
Je conviens certainement que la composition des fonctions est légitime. Par "quelques gars F #" je me réfère à moi-même! C'est mon propre blog. : P
AshleyF

Je ne pense pas que ce .soit équivalent à >>. Je ne sais pas si F # l'a <<mais ce serait l'équivalent (comme dans Elm).
Matt Joiner

28

Si vous voulez utiliser des F # |>dans Haskell, alors Data.Function est l' &opérateur (depuis base 4.8.0.0).


Quel est le motif de Haskell choisir &plus |>? J'ai l'impression que |>c'est beaucoup plus intuitif, et cela me rappelle également l'opérateur de tube Unix.
yeshengm

@yeshengm je trouve &très intuitif. Le code se lit presque correctement en anglais en prononçant simplement &"et". Par exemple: se 5 & factorial & showlit à voix haute comme "prenez 5, puis prenez-en une factorielle et appliquez-y ensuite show".
Marcel Besixdouze il y a

16

Composition de gauche à droite dans Haskell

Certaines personnes utilisent également le style de gauche à droite (passage de message) dans Haskell. Voir, par exemple, la bibliothèque mps sur Hackage. Un exemple:

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

Je pense que ce style a l'air bien dans certaines situations, mais il est plus difficile à lire (il faut connaître la bibliothèque et tous ses opérateurs, le redéfini (.)est aussi dérangeant).

Il existe également des opérateurs de composition de gauche à droite et de droite à gauche dans Control.Category , qui fait partie du package de base. Comparez >>>et <<<respectivement:

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

Il y a une bonne raison de préférer parfois la composition de gauche à droite: l'ordre d'évaluation suit l'ordre de lecture.


Bien, donc (>>>) est largement équivalent à (|>)?
Khanzor

@Khanzor Pas exactement. (|>) applique un argument, (>>>) est principalement une composition de fonction (ou des choses similaires). Ensuite, je suppose qu'il y a une différence de fixité (je ne l'ai pas vérifié).
sastanin le

15

J'ai vu >>>être utilisé pourflip (.) , et je l'utilise souvent moi-même, en particulier pour les longues chaînes qui sont mieux comprises de gauche à droite.

>>> est en fait de Control.Arrow, et fonctionne sur plus que de simples fonctions.


>>>est défini dans Control.Category.
Steven Shaw

13

Outre le style et la culture, cela se résume à optimiser la conception du langage pour du code pur ou impur.

L' |>opérateur est courant dans F # en grande partie parce qu'il permet de masquer deux limitations qui apparaissent avec un code principalement impur:

  • Inférence de type de gauche à droite sans sous-types structurels.
  • La restriction de valeur.

Notez que l'ancienne limitation n'existe pas dans OCaml car le sous-typage est structurel au lieu de nominal, de sorte que le type structurel est facilement affiné via l'unification au fur et à mesure que l'inférence de type progresse.

Haskell prend un compromis différent, choisissant de se concentrer sur du code principalement pur où ces limitations peuvent être levées.


8

Je pense que l'opérateur pipe forward de F # ( |>) devrait vs ( & ) dans 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

Si vous n'aimez pas l' &opérateur ( ), vous pouvez le personnaliser comme F # ou Elixir:

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

Pourquoi infixl 1 |>? Voir la doc dans Data-Function (&)

infixl = infixe + associativité gauche

infixr = infix + associativité droite


(.)

( .) signifie composition de fonction. Cela signifie (fg) (x) = f (g (x)) en mathématiques.

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

c'est égal

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

ou

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

L' $opérateur ( ) est également défini dans Data-Function ($) .

( .) est utilisé pour créer Hight Order Functionou closure in js. Voir exemple:


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

Wow, Less (code) c'est mieux.


Comparez |>et.

ghci> 5 |> factorial |> show

// equals

ghci> (show . factorial) 5 

// equals

ghci> show . factorial $ 5 

C'est la différence entre left —> rightet right —> left. ⊙﹏⊙ |||

Humanisation

|> et & c'est mieux que.

car

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

Comment la programmation fonctionnelle en langage orienté objet?

veuillez visiter http://reactivex.io/

Support Informatique :

  • Java: RxJava
  • JavaScript: RxJS
  • C #: Rx.NET
  • C # (Unity): UniRx
  • Scala: RxScala
  • Clojure: RxClojure
  • C ++: RxCpp
  • Lua: RxLua
  • Rubis: Rx.rb
  • Python: RxPY
  • Aller: RxGo
  • Groovy: RxGroovy
  • JRuby: RxJRuby
  • Kotlin: RxKotlin
  • Swift: RxSwift
  • PHP: RxPHP
  • Elixir: réactif
  • Fléchette: RxDart

1

C'est mon premier jour pour essayer Haskell (après Rust et F #), et j'ai pu définir l'opérateur |> de F #:

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

et cela semble fonctionner:

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

main =     
    5 |> factorial |> print

Je parie qu'un expert Haskell peut vous offrir une solution encore meilleure.


1
vous pouvez également définir des opérateurs d'infixe infix :) x |> f = f x
Jamie
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.