39 votes

À l'aide de rendement contre pas de retour dans la liste monade

J'ai commencé ma Grand Haskell Croisade (GHC :) ) et je suis un peu confus avec des monades et IO fonctions. Quelqu'un pourrait-il expliquer simplement ce qu'est la différence entre ces deux fonctions?

f1 = do x <- [1,2]
        [x, x+1] -- this is monad, right?

f2 = do x <- [1,2]
        return [x, x+1]

Les résultats sont les suivants:

*Main> f1
[1,2,2,3]

*Main> f2
[[1,2],[2,3]]

45voto

pigworker Points 20324

Pour voir pourquoi vous obtenez les réponses particulières qui se posent, le desugaring explications sont très utiles. Permettez-moi de compléter avec un peu de conseils généraux sur le développement des perceptions de code Haskell.

Haskell type de système ne fait pas de distinction entre les deux séparable "morale" à des fins:

  • [x] le type de valeurs qui sont des listes avec des éléments dessinés à partir d' x
  • [x] le type de calculs d'éléments de x qui permettent de priorité choix

Le fait que ces deux notions ont la même représentation ne signifie pas qu'ils jouent les mêmes rôles. En f1, l' [x, x+1] est de jouer le rôle de calcul, de sorte que les possibilités qu'il génère sont fusionnées dans le choix générée par l'ensemble de calcul: c'est ce que l' >>= de la liste monade n'. En f2, cependant, l' [x, x+1] est de jouer le rôle de valeur, de sorte que l'ensemble du calcul génère un ordre de priorité choix entre deux valeurs (qui se trouvent être les valeurs de la liste).

Haskell ne pas utiliser les types de faire cette distinction, [et vous l'aurez deviné, maintenant, que je pense qu'il devrait, mais c'est une autre histoire]. Au lieu de cela, il utilise la syntaxe. Si vous avez besoin de former votre tête de percevoir la valeur de calcul et de rôles, quand vous lisez le code. L' do notation est une syntaxe spéciale pour la construction de calculs. Ce qui se passe à l'intérieur de l' do est construit à partir du modèle suivant kit:

jigsaw pieces for computations

Les trois morceaux de bleu font do-les calculs. J'ai marqué le calcul des trous dans le bleu et la valeur des trous dans le rouge. Ce n'est pas destiné à être une syntaxe complète, juste un guide à la façon de percevoir les morceaux de code dans votre esprit.

En effet, vous pouvez écrire n'importe quel vieux expression dans le bleu de lieux à condition qu'il dispose d'un appareil de type monadique, et le calcul ainsi générées seront fusionnés dans l'ensemble du calcul à l'aide de >>= en tant que de besoin. Dans votre f1 exemple, votre liste est dans un bleu place et traité comme une priorité de choix.

De même, vous pouvez écrire des expressions en rouge les lieux qui peuvent très bien avoir monadique types (comme des listes dans ce cas), mais ils seront traités comme des valeurs tout de même. C'est ce qui arrive en f2: comme c'était le cas, le résultat de l'extérieur des crochets sont bleus, mais les parenthèses sont de couleur rouge.

Entraînez votre cerveau à faire de la valeur/calcul de séparation lorsque vous lisez du code, de sorte que vous savez instinctivement les parties du texte sont en train de faire le travail. Une fois que vous avez reprogrammé votre tête, la distinction entre f1 et f2 semble tout à fait normal!

30voto

mergeconflict Points 4233

Les autres réponses sont correctes, mais je me demande si ils ne sont pas tout à fait ce que vous avez besoin... je vais essayer de garder ce aussi simple que possible, à seulement deux points:


Point 1. return n'est pas quelque chose de spécial dans le langage Haskell. Ce n'est pas un mot clé, et ce n'est pas sucre syntaxique pour quelque chose d'autre. C'est juste une fonction qui fait partie de l' Monad typeclass. Sa signature est simplement:

return :: a -> m a

m est selon la monade, nous parlons à la fois. Il prend un "pur" de la valeur et de la confiture dans votre monade. (Soit dit en passant, il y a une autre fonction appelée pure qui est en fait un synonyme return... j'aime mieux parce que le nom est plus évident!) De toute façon, si m s'agit de la liste de l'errance, alors return a ce type:

return :: a -> [a]

Si cela peut aider, vous pouvez penser que le type synonyme type List a = [a], ce qui pourrait le rendre un peu plus évident que l' List est la chose que nous sommes en remplaçant d' m. De toute façon, si vous alliez mettre en oeuvre return vous-même, le seul moyen raisonnable que vous mettriez c'est en prenant une certaine valeur (quel que soit le type a) et en la collant dans une liste par lui-même:

return a = [a]

Donc, je peux dire return 1 dans la liste monade, et je vais tomber [1]. Je peux dire la même chose return [1, 2, 3] et je vais tomber [[1, 2, 3]].


Point 2. IO est une monade, mais pas toutes les monades sont IO. De nombreux Haskell tutoriels semble faire un amalgame entre les deux sujets en grande partie pour des raisons historiques (d'ailleurs, la même confusion des raisons historiques qui ont conduit à l' return d'être si mal nommée). Il semble que vous pourriez avoir quelques (compréhensible) de la confusion autour de cela.

Dans votre code, vous êtes dans la liste monade parce que vous avez écrit do x <- [1, 2]. Si, au contraire, vous l'aviez écrit, do x <- getLine par exemple, vous seriez dans l' IO monade (parce qu' getLine retours IO String). De toute façon, vous êtes dans la liste de l'errance, de sorte que vous obtenez de la liste de définition de l' return décrit ci-dessus. Vous bénéficiez également de la liste de définition de l' >>=, ce qui est tout a basculé version) concatMap, défini comme suit:

concatMap :: (a -> [b]) -> [a] -> [b]
concatMap f xs = concat (map f xs)

Les autres réponses assez bien en avoir à partir d'ici :) je sais que je ne réponds pas directement à votre question, mais je suis en espérant que ces deux points au lieu adressé des choses fondamentales que vous pourriez avoir de la confusion.

24voto

Will Ness Points 18581

Il est facile de voir lors de la ré-écriture du code avec bind et retour:

[1,2] >>= (\x-> [x,x+1])        === concatMap (\x-> [ x,x+1 ]) [1,2]

[1,2] >>= (\x-> return [x,x+1]) === concatMap (\x-> [[x,x+1]]) [1,2]

Votre premier code est équivalent à l'appel de join sur les résultats de la deuxième, en enlevant un monadique "couche" mis en place par return :: a -> m a, l'amalgame entre la "liste" de la monade en cours d'utilisation, avec la "liste" de votre valeur. Si vous étiez de retour d'une paire de dire, il n'aurait pas fait beaucoup de sens pour omettre l' return:

                                     -- WRONG: type mismatch
[1,2] >>= (\x-> (x,x+1))        === concatMap (\x-> ( x,x+1 )) [1,2]
                                     -- OK:
[1,2] >>= (\x-> return (x,x+1)) === concatMap (\x-> [(x,x+1)]) [1,2]

Ou, nous pouvons utiliser un join/fmap re-écrire:

ma >>= famb === join (fmap famb ma)   -- famb :: a -> m b, "m" here is "[]"

join (fmap (\x-> [x,x+1]) [1,2])        === concat [ [ x,x+1 ] | x<-[1,2]]
join (fmap (\x-> return [x,x+1]) [1,2]) === concat [ [[x,x+1]] | x<-[1,2]]

14voto

Tikhon Jelvis Points 30789

La Liste type ([]) est une monade, oui.

Maintenant, rappelez-vous ce qu' return n'. Il est facile de voir à partir de sa signature de type: return :: Monad m => a -> m a. Nous allons remplacer la liste tapez: return :: a -> [a]. De sorte que cette fonction prend une certaine valeur et qui retourne une liste de valeur. Il est équivalent à \ x -> [x].

Ainsi, dans le premier exemple de code, vous avez une liste à la fin: [x, x+1]. Dans le deuxième exemple, vous avez un imbriquée liste: une liste vient d' [x, x + 1] et une autre liste vient d' return. La ligne return [x, x + 1] pourrait être réécrit pour [[x, x + 1]] dans ce cas.

À la fin, le résultat est la liste de tous les résultats possibles. C'est, nous concaténer le résultat d' x comme 1 et le résultat d' x comme 2 (grâce à l' x <- [1,2] ligne). Ainsi, dans le premier cas, nous concaténer deux listes; dans le second cas, nous concaténer deux listes de listes, parce que les extra - return enveloppé le résultat dans une liste.

8voto

ephemient Points 87003

Desugaring l' do de la syntaxe à l'équivalent

f1 = [1,2] >>= \x -> [x, x+1]
f2 = [1,2] >>= \x -> return [x, x+1]

Maintenant, >>= provient de l' Monad classe,

class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b
    return :: a -> m a

et le membre de GAUCHE de l' >>= dans les deux f1 et f2 est [a] (où a a par défaut Integer), de sorte que nous sommes vraiment en considérant

instance Monad [] where
    (>>=) :: [a] -> (a -> [b]) -> [b]
    ...

Ceci obéit à la même monade lois comme, mais il est une autre monade de,

instance Monad IO where ...

et >>= pour les autres monades, donc, ne pas appliquer aveuglément ce que vous savez à propos de l'un à l'autre, d'accord? :)

instance Monad [] est défini ainsi dans GHC

instance Monad [] where
    m >>= k = foldr ((++) . k) [] m
    return x = [x]
    ...

mais il est probablement plus facile à comprendre, []s' >>= comme

instance Monad [] where
    m >>= k = concatMap k m

Si vous prenez ce et de l'appliquer à l'original, vous obtenez

f1 = concatMap (\x -> [x, x+1]) [1,2]
f2 = concatMap (\x -> [[x, x+1]]) [1,2]

et il est clair pourquoi les valeurs de f1 et f2 sont ce qu'ils sont.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X