web-dev-qa-db-fra.com

Syntaxe des enregistrements Haskell

La syntaxe d'enregistrement de Haskell est considérée par beaucoup comme une verrue sur un langage par ailleurs élégant, en raison de sa syntaxe laide et de la pollution de l'espace de noms. D'un autre côté, c'est souvent plus utile que l'alternative basée sur la position.

Au lieu d'une déclaration comme celle-ci:

data Foo = Foo { 
  fooID :: Int, 
  fooName :: String 
} deriving (Show)

Il me semble que quelque chose dans ce sens serait plus attrayant:

data Foo = Foo id   :: Int
               name :: String
               deriving (Show)

Je suis sûr qu'il doit y avoir une bonne raison pour laquelle je manque, mais pourquoi la syntaxe d'enregistrement de type C a-t-elle été adoptée par rapport à une approche plus propre basée sur la mise en page?

Deuxièmement, y a-t-il quelque chose dans le pipeline pour résoudre le problème d'espace de noms, nous pouvons donc écrire id foo au lieu de fooID foo dans les futures versions de Haskell? (En dehors des solutions de contournement basées sur une classe de type à longue durée actuellement disponibles.)

52
Rob Agar

Eh bien, si personne d'autre ne va essayer, alors je vais essayer de répondre à ces questions (un peu plus attentivement).

tl; dr

Question 1: C'est juste ainsi que les dés ont été lancés. C'était un choix circonstanciel et il est resté.

Question 2: Oui (sorta). Plusieurs parties différentes ont certainement réfléchi à la question.

Lisez la suite pour une explication très longue pour chaque réponse, basée sur des liens et des citations que j'ai trouvé pertinents et intéressants.

Pourquoi la syntaxe d'enregistrement de type C a-t-elle été adoptée par rapport à une approche de mise en page plus propre?

Des chercheurs de Microsoft ont écrit un article History of Haskell . La section 5.6 traite des enregistrements. Je vais citer le premier petit morceau, qui est perspicace:

L'une des omissions les plus évidentes des premières versions de Haskell était l'absence de registres, offrant des champs nommés. Étant donné que les enregistrements sont extrêmement utiles dans la pratique, pourquoi ont-ils été omis?

Les Microsofties répondent alors à leur propre question

La raison la plus forte semble avoir été qu'il n'y avait pas de "bon" design évident.

Vous pouvez lire le document vous-même pour les détails, mais ils disent que Haskell a finalement adopté la syntaxe d'enregistrement en raison de la "pression pour les champs nommés dans les structures de données".

Au moment où la conception Haskell 1.3 était en cours, en 1993, la pression des utilisateurs pour les champs nommés dans les structures de données était forte, de sorte que le comité a finalement adopté une conception minimaliste ...

Vous demandez pourquoi c'est pourquoi c'est? Eh bien, d'après ce que je comprends, si les premiers Haskeller avaient réussi, nous n'aurions peut-être jamais eu la syntaxe d'enregistrement en premier lieu. L'idée a apparemment été poussée sur Haskell par des gens qui étaient déjà habitués à la syntaxe de type C, et étaient plus intéressés à faire entrer des choses de type C dans Haskell plutôt que de faire des choses "à la manière d'Haskell". (Oui, je me rends compte que c'est une interprétation extrêmement subjective. Je peux me tromper, mais en l'absence de meilleures réponses, c'est la meilleure conclusion que je puisse tirer.)

Y a-t-il quelque chose dans le pipeline pour résoudre le problème d'espace de noms?

Tout d'abord, tout le monde ne pense pas que ce soit un problème. Il y a quelques semaines, un Racket passionné m'a expliqué (et d'autres) que le fait d'avoir différentes fonctions avec le même nom était une mauvaise idée, car cela complique l'analyse de "que fait la fonction nommée ___?" Ce n'est pas, en fait, une fonction, mais plusieurs. L'idée peut être extrêmement gênante pour Haskell, car elle complique l'inférence de type.

Sur une légère tangente, les Microsofties ont des choses intéressantes à dire sur les classes de Haskell:

C'était une heureuse coïncidence du moment où Wadler et Blott ont produit cette idée clé au moment même où la conception du langage était encore en cours.

N'oubliez pas qu'Haskell était jeune une fois. Certaines décisions ont été prises simplement parce qu'elles ont été prises.

Quoi qu'il en soit, il existe plusieurs façons intéressantes de résoudre ce "problème":

Type Directed Name Resolution , une proposition de modification de Haskell (mentionnée dans les commentaires ci-dessus). Il suffit de lire cette page pour voir qu'elle touche beaucoup de domaines de la langue. Dans l'ensemble, ce n'est pas une mauvaise idée. Beaucoup de réflexion y a été apportée afin qu'elle ne heurte pas les trucs. Cependant, il faudra encore beaucoup plus d'attention pour l'intégrer dans le langage Haskell maintenant (plus) mature.

Un autre article de Microsoft, OO Haskell , propose spécifiquement une extension du langage Haskell pour prendre en charge la "surcharge ad hoc". C'est assez compliqué, vous n'aurez donc qu'à vérifier la section 4 par vous-même. L'essentiel est d'inférer automatiquement (?) Les types "Has", et d'ajouter une étape supplémentaire pour vérifier les types qu'ils appellent "amélioration", vaguement décrite dans les citations sélectives qui suivent:

Étant donné la contrainte de classe Has_m (Int -> C -> r) il n'y a qu'une seule instance pour m qui correspond à cette contrainte ... Puisqu'il y a exactement un choix, nous devons le faire maintenant, et cela à son tour corrige r à être Int. Par conséquent, nous obtenons le type attendu pour f: f :: C -> Int -> IO Int... [ceci] est simplement un choix de conception, basé sur l'idée que la classe Has_m est fermé

Toutes mes excuses pour la citation incohérente; si cela vous aide du tout, alors tant mieux, sinon allez simplement lire le document. C'est une idée compliquée (mais convaincante).

Chris Done a utilisé le modèle Haskell pour fournir en tapant du canard dans Haskell d'une manière vaguement similaire au OO papier Haskell (en utilisant les types "Has"). Quelques sessions interactives échantillons de son site:

λ> flap ^. donald
*Flap flap flap*
λ> flap ^. chris
I'm flapping my arms!

fly :: (Has Flap duck) => duck -> IO ()
fly duck = do go; go; go where go = flap ^. duck

λ> fly donald
*Flap flap flap*
*Flap flap flap*
*Flap flap flap*

Cela nécessite un peu de syntaxe standard/inhabituelle, et je préférerais personnellement m'en tenir aux classes de caractères. Mais bravo à Chris Done pour avoir librement publié son travail terre-à-terre dans la région.

45
Dan Burton

J'ai juste pensé ajouter un lien sur le problème de l'espace de noms. Il semble que champs d'enregistrement surchargés pour GHC arrivent dans GHC 7.10 (et sont probablement déjà dans HEAD), en utilisant OverloadedRecordFields extension .

Cela permettrait une syntaxe telle que

data Person = Person { id :: Int, name :: String }
data Company { name :: String, employees :: [Person] }

companyNames :: Company -> [String]
companyNames c = name c : map name (employees c)
8
crockeea

[modifier] Cette réponse n'est que quelques réflexions au hasard sur la question. Je recommande mon autre réponse par rapport à celle-ci, car pour cette réponse, j'ai pris beaucoup plus de temps à rechercher et à référencer le travail des autres.

Syntaxe d'enregistrement

Prendre quelques coups dans le noir: votre syntaxe proposée "basée sur la mise en page" ressemble beaucoup à des déclarations sans syntaxe d'enregistrement data; cela pourrait être source de confusion pour l'analyse (?)

--record
data Foo = Foo {i :: Int, s :: String} deriving (Show)
--non-record
data Foo = Foo Int String deriving (Show)
--new-record
data Foo = Foo i :: Int, s :: String deriving (Show)

--record
data LotsaInts = LI {a,b,c,i,j,k :: Int}
--new-record
data LostaInts = LI a,b,c,i,j,k :: Int

Dans ce dernier cas, quelle est exactement :: Int appliqué? La déclaration de données entière?

Les déclarations avec la syntaxe d'enregistrement (actuellement) sont similaires à la syntaxe de construction et de mise à jour. La syntaxe basée sur la mise en page ne serait pas plus claire dans ces cas; comment analysez-vous ces extra = panneaux?

let f1 = Foo {s = "foo1", i = 1}
let f2 = f1 {s = "foo2"}

let f1 = Foo s = "foo1", i = "foo2"
let f2 = f1 s = "foo2"

Comment le sais-tu f1 s est une mise à jour d'enregistrement, par opposition à une application de fonction?

Espaces de noms

Que faire si vous souhaitez mélanger l'utilisation de votre id défini par la classe avec le id du Prelude? Comment spécifiez-vous celui que vous utilisez? Pouvez-vous penser à un meilleur moyen que les importations qualifiées et/ou le mot clé hiding?

import Prelude hiding (id)

data Foo = Foo {a,b,c,i,j,k :: Int, s :: String}
               deriving (Show)

id = i

ghci> :l data.hs
ghci> let foo = Foo 1 2 3 4 5 6 "foo"
ghci> id foo
4
ghci> Prelude.id f1
Foo {a = 1, b = 2, c = 3, i = 4, j = 5, k = 6, s = "foo"}

Ce ne sont pas de bonnes réponses, mais ce sont les meilleures que j'ai. Personnellement, je ne pense pas que la syntaxe d'enregistrement soit si moche. Je pense qu'il y a de la place pour des améliorations avec les choses namespacing/modules, mais je n'ai aucune idée de comment les améliorer.

5
Dan Burton