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
r ∷ r3
but it is being used where an r1
and r2
are required, so there is a type mismatch. A record {a ∷ A, b ∷ B}
cannot be given where {a ∷ A}
or {b ∷ B}
or {}
is expected. However, one can say this:
f ∷ ∀ s r. Row.Cons "a" A s r ⇒ Record r → A
f {a} = a
In words, f
is a function polymorphic on any record containing a label "a"
with type A
. Similarly, you could change eval to:
eval ∷ ∀ s r. Row.Union c s r ⇒ a → Record r → b
In words, eval
is polymorphic on any record which contains at least the fields of c
. This introduces a type ambiguity which you will have to resolve with a proxy.
eval ∷ ∀ proxy s r. Row.Union c s r ⇒ proxy c → a → Record r → b
The eval instance of Add becomes:
instance evalAdd ∷
( Component a Int r1
, Component b Int r2
, Union r1 s1 r3
, Union r2 s2 r3
) => Component (Add a b) Int r3 where
eval _ (Add a b) r = eval (RProxy ∷ RProxy r1) a r + eval (RProxy ∷ RProxy r2) b r
From here, r1
and r2
become ambiguous because they're not determined from r3
alone. With the given constraints, s1
and s2
would also have to be known. Possibly there is a functional dependency you could add. I am not sure what is appropriate because I am not sure what the objectives are of the program you are designing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With