Problem
Consider the following design problem in Haskell. I have a simple, symbolic EDSL in which I want to express variables and general expressions (multivariate polynomials) such as x^2 * y + 2*z + 1. In addition, I want to express certain symbolic equations over expressions, say x^2 + 1 = 1, as well as definitions, like x := 2*y - 2.
The goal is to:
- Have a separate type for variables and general expressions - certain
functions might be applied to variables and not complex expressions.
For instance, a definition operator
:=might be of type(:=) :: Variable -> Expression -> Definitionand it should not be possible to pass a complex expression as its left-hand side parameter (though it should be possible to pass a variable as its right-hand side parameter, without explicit casting). - Have expressions an instance of
Num, so that it's possible to promote integer literals to expressions and use a convenient notation for common algebraic operations like addition or multiplication without introducing some auxiliary wrapper operators.
In other words, I would like to have an implicit and static type cast (coercion) of variables to expressions. Now, I know that as such, there are no implicit type casts in Haskell. Nevertheless, certain object-oriented programming concepts (simple inheritance, in this case) are expressible in Haskell's type system, either with or without language extensions. How could I satisfy both above points while keeping a lightweight syntax? Is it even possible?
Discussion
It is clear that the main problem here is Num's type restriction, e.g.
(+) :: Num a => a -> a -> a
In principle, it's possible to write a single (generalised) algebraic data type for both variables and expressions. Then, one could write := in such a way, that the left-hand side expression is discriminated and only a variable constructor is accepted, with a run-time error otherwise. That's however not a clean, static (i.e. compile-time) solution...
Example
Ideally, I would like to achieve a lightweight syntax such as
computation = do
x <- variable
t <- variable
t |:=| x^2 - 1
solve (t |==| 0)
In particular, I want to forbid notation like
t + 1 |:=| x^2 - 1 since := should give a definition of a variable and not an entire left-hand side expression.