I've been trying to develop a component system in Purescript, using a Component typeclass which specifies an eval function. The eval function for can be recursively called by a component for each sub-component of the component, in essence fetching the input's values.
As components may wish to use run-time values, a record is also passed into eval. My goal is for the rows in the Record argument of the top-level eval to be required to include all the rows of every sub-component. This is not too difficult for components which do not use any rows themselves, but their single sub-component does, as we can simply pass along the sub-components rows to the component's. This is shown in evalIncrement
.
import Prelude ((+), one)
import Data.Symbol (class IsSymbol, SProxy(..))
import Record (get)
import Prim.Row (class Cons, class Union)
class Component a b c | a -> b where
eval :: a -> Record c -> b
data Const a = Const a
instance evalConst :: Component (Const a) a r where
eval (Const v) r = v
data Var (a::Symbol) (b::Type) = Var
instance evalVar ::
( IsSymbol a
, Cons a b r' r) => Component (Var a b) b r where
eval _ r = get (SProxy :: SProxy a) r
data Inc a = Inc a
instance evalInc ::
( Component a Int r
) => Component (Inc a) Int r where
eval (Inc a) r = (eval a r) + one
All of the above code works correctly. However, once I try to introduce a component which takes multiple input components and merges their rows, I cannot seem to get it to work. For example, when trying to use the class Union
from Prim.Row
:
data Add a b = Add a b
instance evalAdd ::
( Component a Int r1
, Component b Int r2
, Union r1 r2 r3
) => Component (Add a b) Int r3 where
eval (Add a b) r = (eval a r) + (eval b r)
The following error is produced:
No type class instance was found for
Processor.Component a3
Int
r35
while applying a function eval
of type Component t0 t1 t2 => t0 -> { | t2 } -> t1
to argument a
while inferring the type of eval a
in value declaration evalAdd
where a3 is a rigid type variable
r35 is a rigid type variable
t0 is an unknown type
t1 is an unknown type
t2 is an unknown type
In fact, even modifying the evalInc
instance to use a dummy Union with an empty row produces a similar error, like so:
instance evalInc :: (Component a Int r, Union r () r1)
=> Component (Increment a) Int r1 where
Am I using Union incorrectly? Or do I need further functional dependencies for my class - I do not understand them very well.
I am using purs version 0.12.0
evalAdd.eval
, you haver :: Record r3
, but the calleval a r
would require thatr :: Record r1
, and similarly, the calleval b r
would require thatr :: Record r2
. So you would get a type mismatch if it was a plain function, but sinceeval
is overloaded, the compiler just says that there is no overload fora
+r3
or forb
+r3
. - Fyodor SoikinMap
whose keys areString
s representingSymbol
s (akin toreflectSymbol
) and whose values have typeValue
.Value
could have a different constructor for each kind of type you're going to store. I understand you'll lose polymorphism but you'll save on the type-level gymnastics... Alternatively you can see if the purescript-variant package meets your needs. - Kartik Sabharwalr1 r2 r3
withr
and removing the union constraint. This achieved the effect of requiring the component to need all the variables its inputs require at compile time at the type level - Joseph Young