2016-11-10 2 views
7

Existe-t-il un sucre syntaxique "de notation" pour la composition de fonction simple?Fonction Composition Do Notation

(c.-à-(.) :: (b -> c) -> (a -> b) -> a -> c)

Je voudrais être en mesure de stocker les résultats de certaines compositions pour plus tard (tout en continuant la chaîne.

Je préfère ne pas utiliser l'extension RebindableSyntax si possible.

Je cherche quelque chose comme ceci:

composed :: [String] -> [String] 
composed = do 
    fmap (++ "!!!") 
    maxLength <- maximum . fmap length 
    filter ((== maxLength) . length) 

composed ["alice", "bob", "david"] 
-- outputs: ["alice!!!", "david!!!"] 

Je ne suis pas sûr que quelque chose comme cela est possible, étant donné que les res ul de la fonction précédente doit essentiellement passer "à travers" la liaison de maxLength, mais je suis ouvert à l'audition de toute autre option similaire expressive. Fondamentalement, j'ai besoin de recueillir des informations que je passe par la composition afin de l'utiliser plus tard.

Peut-être que je pourrais faire quelque chose comme ça avec une monade d'état?

Merci pour votre aide!

Modifier

Ce genre de chose fonctionne un peu:

split :: (a -> b) -> (b -> a -> c) -> a -> c 
split ab bac a = bac (ab a) a 

composed :: [String] -> [String] 
composed = do 
    fmap (++ "!!!") 
    split 
     (maximum . fmap length) 
     (\maxLength -> (filter ((== maxLength) . length))) 
+0

S'il vous plaît préciser ce que la composition que vous essayez réellement d'obtenir ici. À tout le moins, donnez la signature de type que vous voulez pour 'composé '! – leftaroundabout

+0

Juste la composition de la fonction normale, mais avec la possibilité de stocker des résultats interstitiels à utiliser dans les appels de fonction ultérieurs (voir l'exemple). J'ai ajouté le type. –

+1

Y a-t-il une raison pour laquelle vous devez l'écrire en style sans point? Cela devrait être facile dans un style non-point-free. – immibis

Répondre

2

Comme leftaroundabout mentionné, vous pouvez utiliser Arrows pour écrire votre fonction. Mais, il existe une fonctionnalité dans ghc compilateur Haskell, qui est proc -notation pour les flèches. Il est très semblable à do -notation bien connu, mais, malheureusement, peu de gens le savent.

Avec proc -notation vous pouvez écrire la fonction souhaitée dans la prochaine façon plus redable et élégante:

{-# LANGUAGE Arrows #-} 

import Control.Arrow (returnA) 
import Data.List  (maximum) 

composed :: [String] -> [String] 
composed = proc l -> do 
    bangedL <- fmap (++"!!!")  -< l 
    maxLen <- maximum . fmap length -< bangedL 
    returnA -< filter ((== maxLen) . length) bangedL 

Et cela fonctionne dans ghci comme prévu:

ghci> composed ["alice", "bob", "david"] 
["alice!!!","david!!!"] 

Si vous êtes intéressé , vous pouvez lire quelques tutoriels avec de belles images pour comprendre ce qu'est la flèche et comment fonctionne cette fonction puissante pour que vous puissiez y plonger plus profondément:

https://www.haskell.org/arrows/index.html

https://en.wikibooks.org/wiki/Haskell/Understanding_arrows

+1

Bon à mentionner, même si je dirais que la force de la notation 'proc' est qu'il vous permet d'écrire des compositions de flèches assez générales comme si elles étaient des fonctions dans la syntaxe lambda. Si la catégorie sur laquelle vous travaillez est _is_ '(->)', alors il n'y a pas beaucoup de raison de ne pas simplement utiliser la liaison lambdas/'let ... in' pour accomplir la même chose, si vous voulez donner ces intermédiaires résultats des noms de toute façon. – leftaroundabout

+0

@leftaroundabout Oui, il existe des flèches à côté de '(->)' et vous pouvez écrire des compositions de fonctions de version générale bien que je ne l'ai pas fait pour simplifier l'exemple. Votre version suit les pensées de ce blog-post: http://degoes.net/articles/insufficiently-polymorphic Mais parfois avoir des noms de variables peut faciliter la compréhension du code (en particulier pour les flèches). – Shersh

5

Une façon possible de réaliser quelque chose comme ça sont des flèches. Fondamentalement, lorsque vous «stockez des résultats interstitiels», vous divisez simplement le flux d'informations à travers la chaîne de composition. C'est ce que fait le combinateur &&& (fanout).

Ceci n'est certainement pas un bon code humain compréhensible.

Une monade d'état semble autoriser quelque chose de similaire, mais le problème est que le type d'état est fixé via la chaîne monadique du bloc do. Ce n'est pas vraiment assez flexible pour ramasser des valeurs typées différentes tout au long de la chaîne de composition. Alors qu'il est certainement possible de contourner cela (parmi eux, en effet, RebindableSyntax), ce n'est pas non plus une bonne idée IMO.

+0

Hrmm, Intéressant! J'ai essayé quelque chose de similaire dans le montage ci-dessus, j'aimerais quand même voir quelque chose d'un peu plus élégant, c'est un peu gênant:/ –

1

Ce que vous avez est essentiellement un filtre, mais un filtre où la fonction de filtrage change au fur et à mesure que vous parcourez la liste. Je modéliser ce pas une composition « fourchue », mais comme un pli en utilisant la fonction suivante f :: String -> (Int, [String]):

  1. La valeur de retour maintient le courant maximum et toutes les chaînes de cette longueur.
  2. Si le premier argument est plus court que le maximum actuel, supprimez-le.
  3. Si le premier argument est le même que le maximum actuel, ajoutez-le à la liste.
  4. Si le premier argument est plus long, définissez sa longueur comme nouveau maximum et remplacez la liste de sortie actuelle par une nouvelle liste.

Une fois le pli terminé, vous venez d'extraire la liste du tuple.

-- Not really a suitable name anymore, but... 
composed :: [String] -> [String] 
composed = snd . foldr f (0, []) 
    where f curr (maxLen, result) = let currLen = length curr 
            in case compare currLen maxLen of 
             LT -> (maxLen, result)  -- drop 
             EQ -> (maxLen, curr:result) -- keep 
             GT -> (length curr, [curr]) -- reset 
3

Le type de (<*>) spécialisé pour l'instance de fonction de Applicative est:

(<*>) :: (r -> a -> b) -> (r -> a) -> (r -> b) 

La fonction r -> b résultante passe son argument à la fois les r -> a -> b et la r -> a fonctions, et utilise ensuite la valeur a produit par la fonction r -> a comme deuxième argument du r -> a -> b.

Qu'est-ce que cela a à voir avec votre fonction? filter est une fonction de deux arguments, un prédicat et une liste. Maintenant, un aspect clé de ce que vous essayez de faire est que le prédicat est généré à partir de la liste. Cela signifie que le noyau de votre fonction peut être exprimée en termes de (<*>):

-- Using the predicate-generating function from leftaroundabout's answer. 
maxLengthOnly :: Foldable t => [t a] -> [t a] 
maxLengthOnly = flip filter <*> ((. length) . (==) . maximum . fmap length) 

composed :: [String] -> [String] 
composed = maxLengthOnly . fmap (++ "!!!") 

Cette définition maxLengthOnly serait un one-liner très bien si la fonction génératrice prédicat Pointfree étaient pas si maladroits.

Depuis l'instance Applicative des fonctions est équivalent au pouvoir à l'Monad un, maxLengthOnly peut également être formulée comme:

maxLengthOnly = (. length) . (==) . maximum . fmap length >>= filter 

(Le split vous avez ajouté à votre question, par ailleurs, est (>>=) pour les fonctions .)

Une autre façon de l'écrire avec Applicative est:

maxLengthOnly = filter <$> ((. length) . (==) . maximum . fmap length) <*> id 

Ce n'est pas une coïncidence si cela ressemble beaucoup à la solution de leftaroundabout: pour les fonctions, (,) <$> f <*> g = liftA2 (,) f g = f &&& g.

Enfin, il est également intéressant de noter que, alors qu'il est tentant de remplacer id dans la dernière version de maxLengthOnly avec fmap (++ "!!!"), qui ne fonctionnera pas parce que fmap (++ "!!!") modifie la longueur des cordes, et affecte donc le résultat de la prédicat. Avec une fonction qui ne remet pas en cause le prédicat, cependant, il travaillerait assez bien:

nicerComposed = filter 
    <$> ((. length) . (==) . maximum . fmap length) <*> fmap reverse 
GHCi> nicerComposed ["alice","bob","david"] 
["ecila","divad"]