Problème
Considérez le problème de conception suivant dans Haskell. J'ai un EDSL simple et symbolique dans lequel je veux exprimer des variables et des expressions générales (polynômes multivariés) comme x^2 * y + 2*z + 1
. De plus, je veux exprimer certaines équations symboliques sur des expressions, disons x^2 + 1 = 1
, ainsi que des définitions , comme x := 2*y - 2
.
Le but est de:
- Avoir un type distinct pour les variables et les expressions générales - certaines fonctions peuvent être appliquées aux variables et non aux expressions complexes. Par exemple, un opérateur de définition
:=
peut être de type(:=) :: Variable -> Expression -> Definition
et il ne devrait pas être possible de passer une expression complexe comme paramètre de gauche (bien qu'il devrait être possible de passer une variable comme paramètre de droite, sans transtypage explicite ) . - Ayez une expression dans les expressions
Num
, de sorte qu'il est possible de promouvoir des littéraux entiers en expressions et d'utiliser une notation pratique pour les opérations algébriques courantes comme l'addition ou la multiplication sans introduire certains opérateurs d'encapsuleur auxiliaires.
En d'autres termes, j'aimerais avoir un transtypage de type implicite et statique (coercition) de variables en expressions. Maintenant, je sais qu'en tant que tel, il n'y a pas de transtypages implicites dans Haskell. Néanmoins, certains concepts de programmation orientée objet (héritage simple, dans ce cas) sont exprimables dans le système de type de Haskell, avec ou sans extensions de langage. Comment pourrais-je satisfaire les deux points ci-dessus tout en conservant une syntaxe légère? Est-ce même possible?
Discussion
Il est clair que le problème principal ici est Num
la restriction de type de, par exemple
(+) :: Num a => a -> a -> a
En principe, il est possible d'écrire un seul type de données algébrique (généralisé) pour les variables et les expressions. Ensuite, on pourrait écrire :=
de telle manière que l'expression de gauche est discriminée et seul un constructeur de variable est accepté, avec une erreur d'exécution sinon. Ce n'est cependant pas une solution propre et statique (c'est-à-dire au moment de la compilation) ...
Exemple
Idéalement, j'aimerais obtenir une syntaxe légère telle que
computation = do
x <- variable
t <- variable
t |:=| x^2 - 1
solve (t |==| 0)
En particulier, je veux interdire une notation comme
t + 1 |:=| x^2 - 1
puisque :=
devrait donner une définition d'une variable et non une expression entière à gauche.
class FromVar e
avec une méthodefromVar :: Variable -> e
et fournir des instances pourExpression
etVariable
, puis avoir vos variables ont des types polymorphesx :: FromVar e => e
etc. Je n'ai pas testé à quel point cela fonctionne depuis que je suis sur mon téléphone en ce moment.FromVar
classe de caractères serait utile. Je veux éviter les transtypages explicites tout en conservantExpr
une instance deNum
. J'ai édité la question en ajoutant un exemple de notation que j'aimerais atteindre.Réponses:
Pour tirer parti du polymorphisme plutôt que du sous-typage (parce que c'est tout ce que vous avez dans Haskell), ne pensez pas "une variable est une expression", mais "les variables et les expressions ont des opérations en commun". Ces opérations peuvent être placées dans une classe de type:
Ensuite, plutôt que de lancer des choses, rendez les choses polymorphes. Si c'est le cas
v :: forall e. HasVar e => e
, il peut être utilisé à la fois comme expression et comme variable.Squelette pour faire le code ci-dessous typecheck: https://gist.github.com/Lysxia/da30abac357deb7981412f1faf0d2103
la source
V
. Au départ, ce n'était pas ce que je voulais, mais j'ai peut-être été trop rapide pour le rejeter ... Je ne peux probablement pas me débarrasser de l'opaqueV
. Sur une note connexe, comment puis-je créer une instance deV (forall e . HasVar e => e)
? Dans Coq, j'utiliserais des calculs de type et des correspondances de motifs sur un type inductif, mais on ne sait pas comment y parvenir dans Haskell.w :: Variable
certaine façon et d' appliquerfromVar
à elle:variable = (\w -> V (fromVar w)) <$> (_TODO_ :: Solver Variable)
.V
pourrait être évité avec des types imprédicatifs, mais c'est toujours WIP. Ou on peut fairevariable
prendre la suite avec un argument polymorphe explicitement au lieu de indirectement via(>>=)
.