11
votes

Qu'est-ce que je fais mal avec cet exercice de polymorphisme de haut niveau?

Il y a plus d'un an, j'ai posé la question Comment utiliser un proxy à Haskell , et depuis lors, j'ai un Petite quantité d'utilisation de l'extension de HankNTypes GHC. Le problème, c'est chaque fois que j'essaie de travailler avec elle, je me retrouve avec des messages d'erreur bizarre et un piratage autour du code jusqu'à ce qu'ils disparaissent. Ou bien j'abandonne.

évidemment, je ne comprends pas vraiment le polymorphisme de rang supérieur dans Haskell. Pour essayer de résoudre ce problème, j'ai décidé de descendre jusqu'aux exemples les plus simples que je pourrais, de tester toutes mes hypothèses et de voir si je pouvais me procurer un moment d'EUREKA. P>

Première hypothèse - La raison du polymorphisme de rang élevé ISN La fonctionnalité Standard Haskell 98 (ou 2010?) Est-ce que cela, à condition que vous acceptiez quelques restrictions évidentes que beaucoup de programmeurs ne remarqueraient même pas, ce n'est pas nécessaire. Je peux définir les fonctions polymorphes de rang 1 et de rang 2 qui sont, à première vue, équivalentes. Si je les chargez dans GHCI et que je les appelez avec les mêmes paramètres, ils donneront les mêmes résultats. P>

Exemple simple Fonctions ... P>

*Main> ([1,2,3] :: [Int]) :: [a]

<interactive>:15:2:
    Couldn't match type `a1' with `Int'
      `a1' is a rigid type variable bound by
           an expression type signature: [a1] at <interactive>:15:1
    Expected type: [a]
      Actual type: [Int]
    In the expression: ([1, 2, 3] :: [Int]) :: [a]
    In an equation for `it': it = ([1, 2, 3] :: [Int]) :: [a]
*Main>


5 commentaires

"Pour être honnête, j'étais un peu surpris que les fonctions RANK1A et RANK1B ont fonctionné dans ce cas." GHCI applique plus de type plus vaste de défaut que GHC: il donne la liste le type [()] . Vous auriez une erreur de type normalement lors de la compilation.


@ Dave4420: Je ne pense pas que tu voudrais. La défaillance ne s'applique que lorsque le type est contraint d'une manière ou d'une autre, mais ici, il est Forall A. [a] (et le résultat est bool , de sorte que cela ne nécessite pas non plus).


@Vitus Non, GHCI a plus de règles de défaillance que cela. Dave4420 est parfaitement correct. Il fait défaut le type à, en fonction de la version de GHC, () ou tout . Ceci est juste quelque chose que GHCI fait pour essayer de vous laisser exécuter des expressions sans fournir de signatures de type.


@CARL: Oui, je suis conscient que GHCI a plus de règles défaillantes, mais aucune de ces informations ne s'applique ici. Signaler stipule que seules les variables de type ambiguë sont en défaut . Par cette définition, Forall a. [a] est le type précis de préfects qui n'a pas besoin de défaut de défaut, GHCI détend ensuite les classes de type pouvant participer à la défaillance et permet également aux types d'être en panne sur () . Et en effet, GHCI est assez cool sur Forall a. [a] . tout est un détail de mise en œuvre.


Oui, @vitus est correct ici. Les types entièrement polymorphes n'ont pas besoin de défaut parce que ce n'est pas ce qu'ils sont. Le type tout est principalement un espace réservé à GHC utilise en interne pour instancier des types non pertinents à quelque chose de concret lors de la compilation. La défaillance n'est nécessaire que pour les variables de type avec une contrainte de classe de type, car le choix de l'instance fait matière.


3 Réponses :


17
votes

Pensons à ce que le type de Rank2 code> signifie. xxx pré>

Le premier argument à Rank2 code> doit être quelque chose de type FORTEZ A. [a] code>. Le FORALL code> étant le plus à l'extérieur signifie que quiconque obtient em> une telle valeur peut choisir leur choix de A code>. Pensez-y comme prenant un type comme un argument supplémentaire. P>

Ainsi, afin de donner quelque chose comme un argument à Rank2 code>, il doit être une liste dont les éléments peuvent être de tout type em> que l'implémentation interne de Rank2 code> pourrait vouloir. Comme il n'ya aucun moyen de conjurer des valeurs d'un tel type arbitraire, les seules entrées possibles sont les seules entrées possibles sont [] code> ou des listes contenant non défini code>. P>

contraste avec Rank1b CODE>: P>

rank2c :: (forall a. [a -> a]) -> Bool
rank2c x = null x


7 commentaires

Donc - passer tout ce qui est utile à cette fonction et faire quelque chose d'utile avec ce paramètre, je vais certainement avoir besoin d'une sorte de contrainte de frappe de caractères - peut-être Rank2 :: (Forall a. Num A => [A] ) -> bool ?


@ Steve314 à peu près - avec (Forall a. [A]) -> BOOL , vous ne pouvez rien faire avec les éléments. Vous pouvez vérifier les choses sur la longueur, mais c'est à ce sujet.


OK - Je suis un peu surpris parce que la fonction null ne se soucie évidemment pas des types des éléments de la liste. Cependant, alors que j'ai eu l'idée que le paramètre polymorphe a un ensemble de types possibles et que cela a oublié que la callee décide du type à interpréter comme (bien que maintenant vous en parlez, je pense que cela a été mentionné dans une réponse à mon précédent question).


@ Steve314 oui, null ne se soucie pas des types des éléments. C'est pourquoi le général Rank1x fonctionne. Pour Rank2 , vous avez spécifié un type beaucoup plus restrictif, mais vous n'avez pas fourni d'argument qui pourrait avoir le type restrictif Forall A. [a] . Une fois que vous avez spécifié un type moins général que le type inféré, vous limitez les cas d'utilisation possibles de votre fonction.


@Daniel Fischer - OK, il semble que je dois repenser quelques idées que j'avais sur le polymorphisme de rang 1 ainsi que le rang 2.


Merci beaucoup pour le Rank2C exemple. Je vais juste jeter dans un rank2d :: (Forall a. [A] -> [a]) -> BOOL Pour montrer que je pense que je commence à obtenir cette eureka - la Callee peut souvent utilement décidé du type d'un paramètre lorsque ce paramètre est une fonction - un paramètre approprié est simplement une fonction polymorphe ordinaire (rang 1) qui pourrait par exemple Réorganisez les valeurs dans une structure sans attentionné quel type de valeurs il réarrange


@ Steve314: Oui. Tout ce que vous pouvez écrire comme une définition polymorphe de haut niveau pourrait avoir un sens ici. La seule valeur définie avec type Forall a. [a] est [] , mais Forall a. [a] -> [a] pourrait être un tas de choses telles que inverse , cycle , goutte 3 , const [] ... Le point de clé ici est celui avec Rank2C ou Rank2D , le choix d'un type a et une certaine valeur avec ce type sont donnés comme des arguments. Dans votre (code> Rank2 d'origine, seul le type était un "argument", mais il s'attendait à une sortie de ce type.



5
votes

Comparons les signatures de type code> explicitez code> FORAC code> Signatures:

>:t [] 
[] :: [a]


0 commentaires

5
votes

À ce stade, je n'essaie pas de faire quelque chose d'utile avec un rang supérieur polymorphisme. Le point ici est simplement de pouvoir appeler le rang2 fonctionner avec une liste non vide et comprendre pourquoi cela ne fonctionne pas exactement la même chose que les autres fonctions. Je veux continuer à comprendre cette étape par étape moi-même, mais maintenant je suis tout à fait coincé.

Je ne suis pas si sûr que le polymorphisme haut rang est ce que vous pensez que c'est. Je pense que le concept ne fait que sens en ce qui concerne les types de fonctions.

par exemple: xxx

nous dit, que inverse et queue travail indépendamment du type de l'élément de liste. Maintenant, compte tenu de cette fonction: xxx

Quel est le type de foo ? L'inférence de HM standard ne peut pas trouver le type. Spécifiquement, il ne peut pas déduire le type de f . Nous devons aider le vérificateur de type ici et promettre que nous n'acceptons que des fonctions qui ne se soucient pas des éléments de liste des éléments de la liste: xxx

nous pouvons Xxx

et donc avoir un type de rang-2 utilisable. Notez que la signature de type interdit de passer quelque chose comme: xxx

car la fonction transmise n'est pas totalement indépendante du type d'élément: il nécessite des éléments numériques.


3 commentaires

J'ai commenté avant - les supprima parce qu'ils ont suggéré un nouveau malentendu que je préférerais ne pas infecter d'autres personnes. Néanmoins, j'ai accepté, j'avais mal compris le polymorphisme de rang 2 et cela a été aidé par un malentendu du polymorphisme de rang 1 entraînant une pièce d'une fausse analogie avec des modèles C ++. Fondamentalement, une fonction de modèle C ++ spécifie tout un tas de fonctions monomorphes - vous devez savoir lequel l'appellera. Je comprends maintenant que dans Haskell une fonction polymorphe n'est qu'une seule fonction - le type réel doit être corrigé d'une manière ou d'une autre, mais vous n'avez pas à choisir un particulier ...


Mise en œuvre monomorphe pour l'appeler.


De plus, si vous souhaitez utiliser un polymorphisme de rang supérieur avec des données (par opposition aux fonctions), tôt ou tard, vous aurez également besoin de un impréditpolymorphisme , qui est une boule de cire complète. En grande partie parce que GHC ne l'appuie pas entièrement à ce moment-là.