Merci de confirmer ou de corriger mon “interprétation de l'anglais” de cet extrait de code Haskell

StackOverflow https://stackoverflow.com/questions/775730

Question

Je suis un développeur C# qui travaille par l'intermédiaire de "Real World Haskell" afin de vraiment comprendre la programmation fonctionnelle, de sorte que quand j'ai appris F#, je vais vraiment grok, et pas seulement "écrire du code C# F#", pour ainsi dire.

Eh bien, aujourd'hui je suis tombé sur un exemple que j'ai pensé que j'ai entendu 3 fois, seulement ensuite, de voir quelque chose que j'ai oubliée, mise à jour de mon interprétation, et de manière récursive (et la malédiction de trop, croyez-moi).

Maintenant, je crois que je ne comprends effectivement, et j'ai écrit une interprétation de l'anglais" ci-dessous.Pouvez-vous Haskell gourous s'il vous plaît confirmer que la compréhension, ou un point à ce que j'ai raté?

Note:Le Haskell extrait de code (cité directement à partir de l'ouvrage) est la définition d'un type personnalisé qui est destiné à être isomorphe à la construit en Haskell type de liste.

L'extrait de code Haskell

data List a = Cons a (List a)
              | Nil
              defining Show

EDIT:Après quelques réponses, je vois un malentendu que j'ai fait, mais je ne suis pas tout à fait clair sur le Haskell "analyse" des règles qui permettraient de corriger cette erreur.Donc, je ai inclus mon original (incorrect) de l'interprétation ci-dessous, suivie d'une correction, suivi par le reste encore une question pas claire pour moi.

EDIT:Voici mon original (incorrect) "l'anglais de l'interprétation" de l'extrait de

  1. Je suis à la définition d'un type appelé "Liste".
  2. La Liste type est paramétré.Il a un seul paramètre de type.
  3. Il y a 2 valeur des constructeurs qui peut être utilisé pour faire des instances de la Liste.Une valeur constructeur est appelé "Nul" et l'autre valeur constructeur est appelé "Cons".
  4. Si vous utilisez le "Néant" constructeur de valeurs, alors il n'y a pas de champs.
  5. Les "Contre" de la valeur constructeur a qu'un seul paramètre de type.
  6. Si vous utilisez les "Contre" de la valeur constructeur, il y a 2 champs qui doivent être fournis.Le premier champ obligatoire est une instance de la Liste.Le deuxième champ obligatoire est une instance d'une.
  7. (J'ai volontairement omis quelque chose sur la "définition du Spectacle" parce qu'il ne fait pas partie de ce que je veux me concentrer sur le moment).

Le corrigé interprétation serait comme suit (modifications en GRAS)

  1. Je suis à la définition d'un type appelé "Liste".
  2. La Liste type est paramétré.Il a un seul paramètre de type.
  3. Il y a 2 valeur des constructeurs qui peut être utilisé pour faire des instances de Liste.Un constructeur de valeurs est appelé "Nul" et l'autre valeur le constructeur est appelé "Cons".
  4. Si vous utilisez le "Néant" constructeur de valeurs, alors il n'y a pas de champs.

    5.(cette ligne a été supprimé ...il n'est pas précis) Les "Contre" de la valeur constructeur a qu'un seul paramètre de type.

  5. Si vous utilisez les "Contre" de la valeur constructeur, il y a 2 champs qui doivent être fournis.La première champ obligatoire est une instance d'une.Le deuxième est un champ obligatoire exemple de "Liste-de-un".

  6. (J'ai volontairement omis quelque chose sur la "définition du Spectacle" parce qu'il ne fait pas partie de ce que je veux me concentrer sur le moment).

La question qui n'est toujours pas clair

La confusion initiale était l'égard de la partie de l'extrait de code qui lit "Contre une (Liste a)".En fait, c'est la partie qui n'est pas encore clair pour moi.

Les gens ont souligné le fait que chaque élément sur la ligne après le "Contre" jeton est un type, pas une valeur.Ce qui signifie que cette ligne dit "Les Cons valeur constructeur dispose de 2 champs:l'une de type 'a' et l'autre de type 'list-of-a'."

C'est très utile de connaître.Cependant, quelque chose est pas encore clair.Lorsque je créer des instances à l'aide de la Contre valeur de constructeur, ces instances "interpréter" le premier " a " comme signifiant "lieu de la valeur passée ici." Mais ils ne pas interpréter le second " a " de la même manière.

Prenons l'exemple de cette GHCI session:

*Main> Cons 0 Nil
Cons 0 Nil
*Main> Cons 1 it
Cons 1 (Cons 0 Nil)
*Main> 

Quand je tape "Contre 0 Néant", il utilise le "Contre" de la valeur constructeur pour créer une instance de la Liste.De 0, il apprend que le paramètre type est "Entier".Jusqu'à présent, pas de confusion.

Cependant, il aussi détermine que la valeur le premier champ de la Cons est de 0.Pourtant, il détermine rien à propos de l' valeur de la deuxième champ ...elle ne détermine que la seconde a un type de la Liste des "Integer".

Donc ma question est, pourquoi ne "une" dans le premier champ, signifie "le type de ce champ est "a" et la valeur de ce champ est" a", tandis que "a" dans le deuxième champ moyen seulement "le type de ce champ est "Liste de""?

EDIT:Je crois que j'ai maintenant vu la lumière, grâce à plusieurs réponses.Permettez-moi de l'exprimer ici.(Et si en quelque sorte, c'est encore incorrecte, d'une certaine manière, s'il vous plaît par tous les moyens, faites le moi savoir!)

Dans l'extrait de "Contre une (Liste a)", nous disons que les "Contre" de la valeur constructeur a deux champs, et que le premier champ est de type 'a', et que le deuxième champ est de type 'Liste'.

C'est tout ce que nous disons! En particulier, nous sommes en disant: RIEN à propos de les valeurs!C'est un point clé qui me manquait.

Plus tard, nous voulons créer une instance, à l'aide de la "Inconvénients" de la valeur constructeur.Nous tapez ceci dans l'interpréteur:"Contre 0 Néant".Cette explicitement raconte la Contre valeur constructeur à utiliser 0 pour la valeur du premier champ, et d'utiliser Néant, puisque la valeur pour le deuxième champ.

Et c'est tout là est à lui.Une fois que vous savez que la valeur constructeur de définition précise rien de mais des types, tout devient clair.

Merci à tous pour les réponses utiles.Et comme je l'ai dit, si quelque chose est toujours éteint, s'il vous plaît par tous les moyens de me dire à ce sujet.Merci.

Était-ce utile?

La solution

  • Les "Contre" de la valeur constructeur a qu'un seul paramètre de type.

Nope:vous avez déjà paramétrées, quand vous avez déclaré data List a.Un efficace de la propriété, c'est que si j'ai un Néant ::Liste de Int, je ne peux pas d'échange avec un Néant ::Liste De Char.

  • Si vous utilisez les "Contre" de la valeur constructeur, il y a 2 champs qui doivent être fournis.Le premier champ obligatoire est une instance de la Liste.Le deuxième champ obligatoire est une instance d'une.

Vous avez échangés:le premier champ obligatoire est une instance de a, le deuxième champ est une instance de la Liste.

Ce chapitre de Real World Haskell peuvent être d'intérêt.

Merci.C'est le chapitre que je suis en ce moment.Alors ...lorsque le code dit "Contre une (Liste a)", je pensais que les "Contre une" partie de qui était indiquant que la Contre valeur constructeur a été paramétré.Ils n'ont pas encore touché la syntaxe pour paramétrer types, de sorte que j'ai deviné que la syntaxe doit exiger de re-indiquant "un" si vous avez l'intention d'utiliser une.Mais vous dites que c'est pas nécessaire?Et donc ce n'est pas ce que "a" signifie?

Nope.Une fois que nous déclarons un paramètre dans notre typologie, nous obtenons pour les réutiliser autrement-à-dire "ce type doivent être utilisés là-bas." C'est un peu comme un a -> b -> a la signature de type:un est de paramétrer le type, mais puis-je utiliser la même que la valeur de retour.

OK, mais c'est déroutant.Il semble que le premier "a" signifie "le premier champ est une instance de",

Non, c'est pas vrai.Cela signifie simplement que le type de données parametrizes sur un certain type un.

et il signifie AUSSI "le premier champ a la même valeur que la valeur qu'ils passés pour un".En d'autres termes, il spécifie le type ET la valeur.

Non, ce n'est pas vrai non plus.

Voici un exemple instructif, la syntaxe de ce qui vous peut ou peut ne pas avoir vu avant:

foo :: Num a => a -> a

C'est assez standard de signature, pour une fonction qui prend un nombre et fait quelque chose pour elle et vous donne un autre numéro.Ce que je veux dire par "un certain nombre" en Haskell-dire, cependant, est arbitraire de type "a" qui met en œuvre le "Num" de la classe.

Ainsi, cette analyse de l'anglais:

Laisser un indiquer un type de mise en œuvre de la Num typeclass, puis la signature de cette méthode est un paramètre avec le type et la valeur de retour de type a

Quelque chose de semblable se passe avec les données.

Il se produit également pour moi que l'instance de Liste dans le cahier des charges de Cons est déroutant vous:être très prudent lors de l'analyse que:alors que des Inconvénients est la spécification d'un constructeur qui, fondamentalement, est un modèle qui Haskell va encapsuler les données, (Liste a) ressemble à un constructeur, mais est en fait un type, comme Int ou Double.un est un type, PAS une valeur dans tous les sens du terme.

Edit: En réponse à la plus récente édition.

Je pense que la dissection est d'abord appelée pour.Alors je vais faire face à vos questions point par point.

Haskell données constructeurs sont un peu bizarre, parce que vous définir la signature du constructeur, et vous n'avez pas à faire toute autre échafaudage.Les types de données en Haskell n'ont pas de notion de variable membre.(Remarque:il y a une syntaxe alternative que de cette façon de penser est la plus propice aux, mais passons pour l'instant).

Une autre chose est que du code Haskell est dense;son type de signature sont comme ça.Donc nous attendre à voir le même symbole réutilisés dans différents contextes.Type d'inférence joue également un grand rôle ici.

Donc, retour à votre type de:

data List a = Cons a (List a)
              | Nil

J'ai ce morceau en plusieurs parties:

data Une liste

Ceci définit le nom du type, et de tout type paramétré qu'il aura plus tard.Notez que vous ne verrez ce spectacle dans d'autres type de signatures.

Cons a (List a) |
Nul

C'est le nom du constructeur. Ce n'EST PAS un type.Nous pouvons, toutefois, de correspondance de modèle pour elle, ala:

foo :: List a -> Bool
foo Nil = True

Remarquez comment la Liste a est le type dans la signature, et le Néant est à la fois les données constructeur et la "chose", nous en correspondance du modèle pour.

Cons un (Liste a)

Ce sont les types de valeurs que nous avons fente dans le constructeur.Cons a deux entrées, l'une est de type a et un autre est de type Liste un.

Donc ma question est, pourquoi ne "une" dans le premier champ, signifie "le type de ce champ est" a "et la valeur de ce champ est" a", tandis que "a" dans le deuxième champ signifie seulement "le type de ce champ est "Liste de""?

Simple:n'y pensez pas comme nous précisant le type;pensez a Haskell est d'inférence du type hors de lui.Donc, pour nos fins, nous allons coller simplement un 0 dans y, et un Nul dans la deuxième section.Ensuite, Haskell regarde notre code et pense:

  • Hmm, je me demande quel est le type d'Inconvénients, 0 Nul est
  • Eh bien, par Contre est un constructeur de Liste une.Je me demande quel est le type de Liste est un
  • Ainsi, a est utilisé dans le premier paramètre, donc, depuis le premier paramètre est un type Int (une autre mesure de simplification;0 est en fait une chose bizarre qui est typeclassed que Num), donc, c'est à dire une est un Num
  • Hé, eh bien, cela signifie aussi que le type de Néant est la Liste de Int, même si il n'y a rien qui puisse réellement dire que

(Remarque, ce n'est pas réellement comment il est mis en œuvre.Haskell peut faire beaucoup de choses étranges, tandis que l'inférence de types, qui est en partie pourquoi les messages d'erreur sucer.)

Autres conseils

Analogies manquent généralement de toutes sortes de façons, mais comme vous le savez C # Je pensais que cela pourrait être utile.

Voici comment je décrirais la définition de List a en C #, peut-être que cela efface certaines choses (ou plus probablement, vous embrouille encore plus).

class List<A>
{
}

class Nil<A> : List<A>
{
    public Nil() {}
}

class Cons<A> : List<A>
{
    public A Head;
    public List<A> Tail;

    public Cons(A head, List<A> tail)
    {
        this.Head = head;
        this.Tail = tail;
    }
}

Comme vous pouvez le voir;

  • le type de List a un seul paramètre de type (<A>),
  • le constructeur de Nil n'a pas encore de paramètres,
  • et le constructeur de Cons a deux paramètres, un head de valeur de type A et un tail de valeur de type List<A>.

Maintenant, en Haskell et le Nil Cons ne sont que des constructeurs pour le type de données List a, en C #, ils sont aussi des types en eux-mêmes, de sorte que l'analogie est là échoue.

Mais j'espère que cela vous donne une idée intuitive de ce que les différents représentent des années A.

(Et s'il vous plaît faire un commentaire sur la façon dont cette comparaison terrible ne rend pas justice aux types de données de Haskell.)

Cons a (List a)

Le premier champ d'une Cons est une valeur de type « a ». La seconde est une valeur de type « List a », à savoir une liste paramétrées avec le même type que le paramètre de la liste courante.

5 est faux, et je dirais que 6 comme suit pour remplacer les deux:

Cons {1} a {2} (Liste a) {3} est un constructeur appelé Cons (partie avant de {1}) pour une valeur de liste de type a (la liste de données d'une partie) qui a besoin de deux valeurs: un de type a (la partie entre {1} et {2}) et une liste de type de un (la partie entre {2} et {3}).

Pour vous aider à une source de confusion apparente: en Haskell vous avez presque jamais de donner des paramètres de type explicites - inférence de type infère les types de vos valeurs. Dans un sens, oui, lorsque vous passez une valeur à une fonction ou d'un constructeur, vous spécifiez également un type, à savoir. le type de la valeur passée en.

Ouais la syntaxe des données est un peu déroutant, car il calembours les noms et les types et ne fait pas vraiment syntaxique distinction entre eux. En particulier, dans une définition de constructeur comme:

Cons a (List a)

Le premier mot est le nom du constructeur; tout autre mot est le nom d'un certain type prédéfinition. Ainsi, les deux a et List a sont déjà portée (la a a été amené à la portée du a dans « data List a »), et vous dites que ce sont les types de paramètres. Leur rôle pourrait être mieux démontré en déclarant en utilisant la même chose que Enregistrer la syntaxe :

Cons { headL :: a, tailL :: List a }

i.e.. une valeur de type List Int, si il a été construit avec le constructeur de Cons, a deux champs: un Int et un List Int. Si elle a été construite avec Nil, il n'a pas de champ.

  

Quand je tape « Cons 0 Nil », il utilise le constructeur de valeur « Cons » pour créer une instance de la liste. De 0, il apprend que le paramètre de type est « Integer ». Jusqu'à présent, pas de confusion.

     

Cependant, il détermine également que la valeur du premier champ des inconvénients est 0. Pourtant, il détermine rien la valeur du deuxième champ ... il détermine seulement que le second champ a un type de « Liste Entier » .

Non, il détermine que la valeur du deuxième champ est Nil. Compte tenu de votre définition, Nil est une valeur de la List a de type. Par conséquent est si Cons 0 Nil. Et Cons 1 it la valeur du deuxième champ est it; à savoir, Cons 0 Nil. Ceci est précisément ce que l'REPL montre: Cons 1 (Cons 0 Nil).

Je regardais votre question édité.

  

Quand je crée des instances en utilisant les inconvénients   constructeur de valeur, ces instances   « Interpréter » le premier « a » en ce sens   « Placer la valeur passée ici.

Dans "Contre une (Liste a)", à la fois "un" et "Liste" sont un type. Je ne comprends pas ce que la « valeur » doit avec elle.

  

Quand je tape « Contre 0 Néant », il utilise le   « Cons » constructeur de valeur pour créer un   instance de liste. De 0, il apprend   que le paramètre de type est « entier ».   Jusqu'à présent, pas de confusion.

     

Cependant, il détermine aussi que le   La valeur du premier champ des inconvénients   est 0. Pourtant, il détermine rien   la valeur du deuxième champ ... il   ne détermine que le second champ   a un type de "Liste Entier".

La valeur du second champ est Nil.

  

Alors, ma question est, pourquoi « a » dans le   premier champ signifie « le type de cette   le terrain est « a » et la valeur de cette   champ « a » », tandis que « a » dans la deuxième   champ signifie que « le type de cette   champ est «Liste d'un"?

« a » dans le premier champ signifie « le type de ce champ est « un ». » « Liste d'un » dans le second champ signifie « le type de ce champ est « Liste d'un » ». Dans le cas de « Contre 0 Néant » ci-dessus, « a » est déduit d'être « entier ». Donc, "Moins d'une (Liste a)" devient "Cons Entier (Liste Entier)". Le 0 est une valeur de type entier. Le Nil est une valeur de type « Liste Entier ».

  

la valeur de ce champ est 'a'

Je ne comprends pas ce que vous entendez par là. 'A' est une variable de type; qu'est-ce que cela a à voir avec les valeurs?

Juste pour vous donner une « aide » supplémentaire, au cas où vous regardez encore ce fil. Haskell a deux conventions qui gâchera les idées des autres de la façon dont les choses devraient faire - en Haskell, un type paramétrés est si communément accepté, qu'il est généralement considéré comme étant une fonction de niveau de type. De même, les constructeurs de valeurs sont considérées comme des fonctions « spéciaux », qui permettent aussi la recherche de motifs, en plus de leur « prendre une valeur (ou plus) et de produire une valeur en tant que résultat. »

Une autre « drôle » caractéristique de Haskell est qu'il ne pas explicitement (ou implicitement) évaluer les arguments à une fonction, même si cet argument est entre parenthèses . Permettez-moi de dire que peu différemment: les fonctions Haskell n'évaluent pas d'arguments entre parenthèses avant d'autres arguments. Les arguments sont mis entre parenthèses pour fins de regroupement, et non les ont évalués « d'abord ». Haskell attribue des arguments à ( « applique ») une fonction à une priorité plus élevée que toute autre opération - même supérieure à une application de fonction implicite d'un de ses propres arguments. Ceci est la raison pour laquelle le constructeur a Cons parens autour du second argument, (List a) - dire au compilateur que Cons a deux arguments, et non trois . Les parenthèses sont pour le regroupement, et non pour priorité!

Comme peu d'un sujet secondaire, soyez prudent avec les types en F #. Puisque F # a ses racines dans ML, ses types paramétrés ont les paramètres devant - int list, non (List Int) à l'arrière! Haskell il fait dans l'autre sens, car il est de la même façon que Haskell ne fonctionne - d'abord la fonction, puis des arguments à la fonction. Cela encourage un modèle d'usage courant, et explique pourquoi les types Haskell et les constructeurs de valeur sont capitalisés -. Pour vous rappeler que vous avez affaire à un type / chose liée à la classe

D'accord, je suis fait; Merci de me laisser mettre du texte « ce grand mur O sur votre propriété ...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top