Oui. C'est ce qu'on appelle le «style de passage du dictionnaire». Parfois, lorsque je fais des choses particulièrement délicates, je dois supprimer une classe de types et la transformer en dictionnaire, car la transmission de dictionnaire est plus puissante 1 , mais souvent assez lourde, ce qui rend le code conceptuellement simple assez compliqué. J'utilise le style de passage de dictionnaire parfois dans des langues qui ne sont pas Haskell pour simuler des classes de caractères (mais j'ai appris que ce n'est généralement pas une aussi bonne idée qu'il y paraît).
Bien sûr, chaque fois qu'il y a une différence de puissance expressive, il y a un compromis. Bien que vous puissiez utiliser une API donnée de plusieurs manières si elle est écrite à l'aide de DPS, l'API obtient plus d'informations si vous ne le pouvez pas. Dans la pratique, cela apparaît dans Data.Set
, qui repose sur le fait qu'il n'y a qu'un seul Ord
dictionnaire par type. Le Set
stocke ses éléments triés selon Ord
, et si vous créez un ensemble avec un dictionnaire, puis insérez un élément en utilisant un autre, comme cela serait possible avec DPS, vous pourriez casser Set
l'invariant et le faire planter. Ce problème d'unicité peut être atténué en utilisant une existentielle fantômetapez pour marquer le dictionnaire, mais, encore une fois, au prix d'une complexité gênante dans l'API. Cela apparaît également à peu près de la même manière dans l' Typeable
API.
Le bit d'unicité ne revient pas très souvent. Les classes de caractères sont excellentes pour écrire du code pour vous. Par exemple,
catProcs :: (i -> Maybe String) -> (i -> Maybe String) -> (i -> Maybe String)
catProcs f g = f <> g
qui prend deux "processeurs" qui prennent une entrée et pourraient donner une sortie, et les concatène, aplatissant Nothing
, devraient être écrits en DPS quelque chose comme ceci:
catProcs f g = (<>) (funcSemi (maybeSemi listSemi)) f g
Nous avons essentiellement dû préciser le type dans lequel nous l'utilisons à nouveau, même si nous l'avons déjà précisé dans la signature de type, et même cela était redondant car le compilateur connaît déjà tous les types. Parce qu'il n'y a qu'une seule façon de construire un donné Semigroup
à un type, le compilateur peut le faire pour vous. Cela a un effet de type "intérêt composé" lorsque vous commencez à définir un grand nombre d'instances paramétriques et à utiliser la structure de vos types pour calculer pour vous, comme dans les Data.Functor.*
combinateurs, et cela est utilisé avec un grand effet avec deriving via
lequel vous pouvez essentiellement obtenir toutes les structure algébrique "standard" de votre type écrite pour vous.
Et ne me lancez même pas sur MPTC et fundeps, qui réinjectent des informations dans la vérification typographique et l'inférence. Je n'ai jamais essayé de convertir une telle chose en DPS - je suppose que cela impliquerait de passer beaucoup de preuves d'égalité de type - mais en tout cas je suis sûr que ce serait beaucoup plus de travail pour mon cerveau que je ne serais à l'aise avec.
-
1 A moins que vous ne les utilisiez, reflection
auquel cas elles deviennent équivalentes en puissance - mais reflection
peuvent également être lourdes à utiliser.