63 votes

Qu'est-ce que () dans Haskell, exactement?

Je lis Learn You a Haskell et, dans les chapitres de la monade, il me semble que () est traité comme une sorte de "null" pour chaque type. Quand je vérifie le type de () dans GHCi, je reçois

  >>:t ()
() :: ()
 

ce qui est une déclaration extrêmement déroutante. Il semble que () soit un type à lui tout seul. Je ne comprends pas comment cela s'intègre dans la langue et comment il semble être capable de représenter n'importe quel type.

133voto

pigworker Points 20324

tl;dr () ne pas ajouter une valeur "null pour chaque type, l'enfer non; () est un "terne" valeur dans un type de son propre: ().

Permettez-moi de prendre du recul à partir de la question un instant et l'adresse d'une source de confusion. Un truc important à absorber lors de l'apprentissage de Haskell est la distinction entre l' expression de la langue et de son type de langue. Vous êtes probablement au courant que les deux sont séparés. Mais que permet le même symbole pour être utilisés dans les deux, et c'est ce qui se passe ici. Il y a de simples indices textuels pour vous dire la langue dans laquelle vous êtes en train de regarder. Vous n'avez pas besoin d'analyser l'ensemble de la langue pour la détection de ces signaux.

Le haut niveau de Haskell module de vie, par défaut, dans le langage d'expression. Vous définissez les fonctions à écrire les équations entre les expressions. Mais quand vous voyez foo :: bar dans le langage d'expression, cela signifie que foo est l'expression et la barre est son type. Donc, quand vous lisez () :: (), vous voyez une déclaration qui concerne l' () dans l'expression de la langue avec l' () dans le type de langue. Les deux () symboles signifient des choses différentes, parce qu'ils ne sont pas dans la même langue. Cette répétition souvent source de confusion pour les débutants, jusqu'à ce que l'expression/type de langue séparation intalls lui-même dans son subconscient, à quel point il devient utilement mnémonique.

Le mot-clé data introduit un nouveau type de données de la déclaration, impliquant un mélange soigneux de l'expression et le type de langues, comme il est dit d'abord ce que le nouveau type est, et, deuxièmement, quelles sont ses valeurs.

les données TyCon tyvar ... tyvar = ValCon1 type ... type | ... | ValConn type type ... 

Dans cette déclaration, constructeur de type TyCon est ajoutée à la type de la langue et de la ValCon valeur les constructeurs sont en train d'être ajouté à l'expression de la langue (et son modèle de sous-langue). En data déclaration, les choses qui s'opposent à l'argument des lieux de la ValCon s vous dire le type donné les arguments lorsque que ValCon est utilisé dans les expressions. Par exemple,

data Tree a = Leaf | Node (Tree a) a (Tree a)

déclare un constructeur de type Tree pour arbre binaire types de stockage des éléments aux nœuds, dont les valeurs sont données en valeur les constructeurs Leaf et Node. J'aime la couleur type de constructeurs (Arbre) de bleu et de la valeur de constructeurs (Leaf, Node) en rouge. Il devrait y avoir pas de bleu dans les expressions et (sauf si vous utilisez des fonctionnalités avancées) pas de rouge dans les types de. Le type Bool pourrait être déclarée,

data Bool = True | False

ajouter du bleu Bool le type de langue, et rouge True et False de l'expression de la langue. Malheureusement, mon markdown-fu est insuffisante pour la tâche d'ajouter des couleurs à ce poste, de sorte que vous aurez à apprendre à ajouter les couleurs dans votre tête.

L ' "unité" type utilise () comme un symbole particulier, mais il fonctionne que s'il est déclaré

data () = ()  -- the left () is blue; the right () is red

ce qui signifie qu'une théorie bleu () est un constructeur de type dans le genre de la langue, mais qu'une théorie, rouge, () est un constructeur de valeurs dans l'expression de la langue, et en effet () :: (). [Il n'est pas le seul exemple d'un jeu de mots. Les types de la plus grande des tuples suivent le même modèle: paire de syntaxe est comme si

data (a, b) = (a, b)

l'ajout de (,) à la fois le type et expression, langues. Mais je m'éloigne du sujet.]

Donc, le type (), souvent prononcé "Unité", est un type contenant une valeur qui mérite d'être parlant d': cette valeur est écrite () , mais dans le langage d'expression, et est parfois prononcé "vide". Un type avec une seule valeur n'est pas très intéressant. Une valeur de type () contribue à zéro des bits d'information: vous savez déjà ce qui doit être. Ainsi, alors qu'il n'est rien de spécial à propos du type () à indiquer des effets secondaires, il apparaît souvent que la valeur de la composante dans un type monadique. Monadique opérations ont tendance à avoir des types qui ressemblent à des

val-de-type-1 -> ... -> val-de-type-n -> effets-la monade val-hors-type

où le type de retour est un type d'application: la fonction vous indique quels sont les effets possibles et l'argument que vous dit ce genre de valeur est produite par l'opération. Par exemple,

put :: s -> State s ()

ce qui est lu (parce que d'application associés à gauche ["comme nous l'avons tous fait dans les années soixante", Roger Hindley]) comme

put :: s -> (State s) ()

a une entrée de valeur type s, l'effet-monade State s, et la valeur de sortie de type (). Quand vous voyez () de la valeur que le type de sortie, qui signifie simplement "cette opération est utilisée uniquement pour son effet; la valeur est inintéressant". De la même façon

putStr :: String -> IO ()

fournit une chaîne d' stdout mais ne retourne rien d'excitant.

L' () type est également utile comme un type d'élément pour les conteneurs comme des structures, où il indique que les données se compose simplement d'une forme, avec pas intéressant de charge utile. Par exemple, si Tree est déclarée comme ci-dessus, puis Tree () est le type d'arbre binaire de formes, de stockage de rien d'intérêts au niveau des nœuds. De même, [()] est le type de listes d'terne éléments: et si il n'y a rien d'intéressant dans une liste d'éléments, alors la seule information dont il contribue, c'est sa durée.

Pour résumer, () est un type. Sa seule valeur, (), arrive à avoir le même nom, mais c'est ok parce que le type et l'expression sont des langues distinctes. Il est utile d'avoir un type qui représente "pas d'information" parce que, dans le contexte (par exemple, d'une monade ou un conteneur), il vous indique que seul le contexte est intéressant.

31voto

Neil Brown Points 2105

L' () type peut être considéré comme un zéro-élément d'un tuple. C'est un type qui ne peut avoir qu'une seule valeur, et donc il est utilisé si vous avez besoin d'avoir un type, mais vous n'avez pas réellement besoin de transmettre toute information. Voici quelques utilisations possibles.

Monadique des choses comme IO et State ont une valeur de retour, ainsi que l'exécution d'effets secondaires. Parfois, le seul point de l'opération est de réaliser un côté-effet, comme l'écrit à l'écran ou le stockage d'une partie de l'état. Pour écrire à l'écran, putStrLn doit être de type String -> IO ? -- IO toujours a avoir un certain type de retour, mais ici il n'y a rien d'utile pour le retour. Donc, ce type devrait nous revenir? On pourrait dire de type Int, et retourne toujours 0, mais c'est trompeur. Si nous revenons (), le type qui n'a qu'une valeur (et donc pas utile de l'information), pour indiquer qu'il n'y a rien d'utile de revenir.

Il est parfois utile d'avoir un type qui ne peut avoir de valeurs utiles. Demandez-vous si vous avais mis en place un type Map k v cartes touches de type k pour les valeurs de type v. Ensuite, vous souhaitez mettre en place un Set, ce qui est vraiment semblable à une carte, sauf que vous n'avez pas besoin de la valeur de la partie, juste les touches. Dans un langage comme Java, vous pouvez utiliser les booléens comme le mannequin le type de valeur, mais vraiment, vous voulez juste un type qui n'a pas de valeurs utiles. Donc on peut dire type Set k = Map k ()

Il convient de noter que () n'est pas magique. Si vous le souhaitez, vous pouvez la stocker dans une variable et faire une mise en correspondance du modèle sur elle (même si il n'y a pas beaucoup de point):

main = do
  x <- putStrLn "Hello"
  case x of
    () -> putStrLn "The only value..."

12voto

thSoft Points 5513

Il est appelé l' Unit type, habituellement utilisé pour représenter les effets secondaires. Vous pouvez penser vaguement qu' Void en Java. Lire plus ici et ici etc. Ce qui peut être déroutant, c'est qu' () syntaxiquement représente à la fois le type et sa seule valeur littérale. Notez également qu'il n'est pas semblable à l' null en Java, ce qui signifie une référence non définie - () est juste effectivement un 0 de taille n-uplet.

8voto

Ben Points 22160

J'aime à penser, d' () , par analogie avec les tuples.

(Int, Char) est le type de toutes les paires d' Int et Char, c'est donc les valeurs sont toutes les valeurs possibles de l' Int croisé avec toutes les valeurs possibles de l' Char. (Int, Char, String) est de même, le type de tous les triplets de l' Int, un Char, et un String.

Il est facile de voir comment conserver l'extension de ce modèle à la hausse, mais qu'en est vers le bas?

(Int) serait le "1-tuple de type", composé de toutes les valeurs possibles de l' Int. Mais que serait analysé par Haskell comme juste de mettre des parenthèses autour de Int, et donc d'être juste le type Int. Et des valeurs de ce type serait (1), (2), (3), etc, qui a aussi voudrais juste avoir analysé comme ordinaire, Int (les valeurs entre parenthèses. Mais si vous pensez à ce sujet, un "1-n-uplet" est exactement la même que la juste valeur unique, donc il n'y a pas besoin de faire réellement exister.

En descendant d'un pas de plus vers zéro tuples nous donne (), ce qui devrait être sur toutes les combinaisons possibles de valeurs dans une liste vide de types. Eh bien, il y a exactement une façon de faire, qui est pour ne pas contenir d'autres valeurs, il devrait donc y avoir qu'une seule valeur dans le type (). Et par analogie avec tuple de la valeur de la syntaxe, on peut écrire cette valeur en (), qui certes ressemble comme un tuple contenant pas de valeurs.

C'est exactement comment il fonctionne. Il n'y a pas de magie, et ce type () et sa valeur () sont en aucun cas traitées spécialement par le langage.

() n'est pas dans le fait d'être traité comme "une valeur null pour n'importe quel type de" dans les monades des exemples dans le LYAH livre. Chaque fois que le type () est utilisé, la seule valeur qui peut être retourné est - (). Il est donc utilisé comme un type explicitement dire qu'il ne peut pas être toute autre valeur de retour. Et de même, lorsqu'un autre type est censé être renvoyés, vous ne peut pas revenir ().

La chose à garder à l'esprit est que, quand un tas de monadique calculs sont composés avec do des blocs ou des opérateurs comme >>=, >>, etc, ils vont être la construction d'une valeur de type m a pour certains monade m. Ce choix d' m doit rester la même tout au long de l'composants (il n'y a aucune façon de composer une Maybe Int avec un IO Int de la sorte), mais l' a peut et c'est très souvent différents à chaque étape.

Alors, quand quelqu'un qui colle un IO () dans le milieu de l' IO String calcul, ce n'est pas à l'aide de l' () comme valeur null dans l' String type, c'est tout simplement à l'aide d'un IO () sur le chemin de la construction d'un IO String, de la même manière que vous pourriez utiliser un Int sur le chemin de la construction d'un String.

6voto

comonad Points 1852

La confusion provient d'autres langages de programmation: "vide" signifie dans la plupart des imerative langues qu'il n'existe aucune structure dans la mémoire stockant une valeur. Il semble incohérent parce que "boolean" a 2 valeurs au lieu de 2 bits, alors que le "vide" n'a pas de bits au lieu de pas de valeurs, mais il est sur qu' une fonction renvoie dans un sens pratique. Pour être exact: sa seule valeur consomme pas de bit de stockage.

Nous allons ignorer la valeur inférieure (écrites _|_) pour un moment...

() est appelé à l'Unité, à l'écrit comme un null-tuple. Il y a une seule valeur. Et il n'est pas appelé Voidcar Void a même pas de valeur, ne pouvaient donc pas être renvoyées par une fonction.


Observer ceci: Bool a 2 valeurs (True et False), () a une valeur (()), et Void n'a pas de valeur (il n'existe pas). Ils sont comme des ensembles avec deux/un/n éléments. Le moins de mémoire ils ont besoin de stocker leur valeur est 1 peu / pas de bit / impossible, respectivement. Ce qui signifie qu'une fonction qui retourne un () peut revenir avec un résultat de valeur (le plus évident) qui peut être inutile pour vous. Void d'autre part, implique que cette fonction ne reviendra jamais et jamais ne vous donne aucun résultat, parce qu'il n'existe aucun résultat.

Si vous souhaitez donner de la "valeur" d'un nom, qu'une fonction renvoie la qui ne revient jamais (oui, cela ressemble à crazytalk), ensuite l'appeler à la fin ("_|_", écrit comme un T renversé). Il pourrait s'agir d'une exception ou d'une boucle infini ou de blocage ou de "juste attendre plus longtemps". (Certaines fonctions ne puis retour en bas, ssi l'un de leurs paramètres est bas.)

Lorsque vous créez le produit cartésien / un n-uplet de ces types, vous pourrez observer le même comportement: (Bool,Bool,Bool,(),()) a 2·2·2·1·1=6 différentes valeurs. (Bool,Bool,Bool,(),Void) , c'est comme l'ensemble {t,f}×{t,f}×{t,f}×{u}×{} qui a 2·2·2·1·0=0 ces éléments, sauf si vous comptez _|_ comme valeur.

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