2017-10-08 5 views
13

Haskell a Functor, Applicative et Monad instances définies pour les fonctions (en particulier le type partiellement appliquée (->) a) dans la bibliothèque standard, construite autour de la composition de fonctions.cas d'utilisation pour foncteur/applicatif/instances de monad pour les fonctions

Comprendre ces instances est un bon exercice d'esprit-bender, mais ma question ici est sur les utilisations pratiques de ces instances. Je serais heureux d'entendre parler de scénarios réalistes où les gens les utilisaient pour un code pratique.

+7

Le [Lecteur monade] (http://hackage.haskell.org/package/mtl/docs/Control-Monad-Reader.html) est fondamentalement juste un wrapper autour newtype '(->)'. – melpomene

+0

Je les utilise tout le temps. Probablement vous le faites vous-même sans le savoir: '.' est juste' fmap'. – Bergi

+0

@Bergi: bien sûr, je suppose que la question est pourquoi les utiliser au lieu de simplement utiliser (.) –

Répondre

3

Parfois, vous souhaitez traiter les fonctions de la forme a -> m b (où m est un Applicative) en tant que Applicative s eux-mêmes. Cela arrive souvent lors de l'écriture de validateurs ou de parseurs.

Une façon de faire est d'utiliser Data.Functor.Compose, qui se greffe sur les Applicative cas de (->) a et m pour donner une instance Applicative pour la composition:

import Control.Applicative 
import Data.Functor.Compose 

type Star m a b = Compose ((->) a) m b 

readPrompt :: Star IO String Int 
readPrompt = Compose $ \prompt -> do 
    putStrLn $ prompt ++ ":" 
    readLn 

main :: IO() 
main = do 
    r <- getCompose (liftA2 (,) readPrompt readPrompt) "write number" 
    print r 

Il existe d'autres moyens, comme la création de votre propre newtype ou en utilisant ready-madenewtypes à partir de base ou d'autres bibliothèques.

7

Un modèle commun impliquant des instances de fonctions Functor et Applicative est par exemple (+) <$> (*2) <*> (subtract 1). Ceci est particulièrement utile lorsque vous devez alimenter une série de fonctions avec une seule valeur. Dans ce cas, ce qui précède est équivalent à \x -> (x * 2) + (x - 1). Bien que ce soit très proche de LiftA2, vous pouvez étendre ce modèle indéfiniment. Si vous avez une fonction f pour prendre 5 paramètres comme a -> a -> a -> a -> a -> b vous pouvez faire comme f <$> (+2) <*> (*2) <*> (+1) <*> (subtract 3) <*> (/2) et le nourrir avec une seule valeur. Juste comme dans le cas ci-dessous;

Prelude> (,,,,) <$> (+2) <*> (*2) <*> (+1) <*> (subtract 3) <*> (/2) $ 10 
(12.0,20.0,11.0,7.0,5.0) 

Edit: crédit pour une nouvelle commentaire de @Will Ness pour un commentaire de mes sous un autre sujet, vient ici une belle utilisation de fonctions sur applicative;

Prelude> let isAscending = and . (zipWith (<=) <*> drop 1) 
Prelude> isAscending [1,2,3,4] 
True 
Prelude> isAscending [1,2,5,4] 
False 
+0

Merci pour l'entrée. Donc, l'exemple de 5 tuples que vous avez posté. Il peut être réécrit comme '(\ x -> (x + 2, x * 2, x + 1, x-3, x/2)) 10 '- l'avantage de l'applicatif ici est qu'il n'y a pas besoin de répéter 'x'? –

+0

@Eli Bendersky La principale différence est, dans la forme applicative si vous savez combien de paramètres la fonction principale prend, l'ensemble est fonctionnellement décomposable (que vous pouvez composer dynamiquement les paramètres comme vous le souhaitez) tandis que sous forme lambda vous devez avoir une fonction lambda solide à portée de main. – Redu

+0

Voulez-vous dire que nous avons déjà une fonction, comme '(,,,,)' et ne pas avoir à écrire le lambda à la main? –