Jos*_*ung 6 typeclass purescript row-polymorphism
我一直在尝试使用指定 eval 函数的 Component 类型类在 Purescript 中开发组件系统。eval 函数可以由组件的每个子组件递归调用,实质上是获取输入的值。
由于组件可能希望使用运行时值,因此还向 eval 传递了一条记录。我的目标是要求顶级 eval 的 Record 参数中的行包含每个子组件的所有行。对于本身不使用任何行的组件来说,这并不是太困难,但它们的单个子组件确实如此,因为我们可以简单地将子组件行传递给组件。这显示在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
Run Code Online (Sandbox Code Playgroud)
以上所有代码都可以正常工作。但是,一旦我尝试引入一个接受多个输入组件并合并它们的行的组件,我似乎无法让它工作。例如,当尝试使用class Unionfrom 时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)
Run Code Online (Sandbox Code Playgroud)
产生以下错误:
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
Run Code Online (Sandbox Code Playgroud)
事实上,即使修改evalInc实例以使用带有空行的虚拟联合也会产生类似的错误,如下所示:
instance evalInc :: (Component a Int r, Union r () r1)
=> Component (Increment a) Int r1 where
Run Code Online (Sandbox Code Playgroud)
我是否错误地使用了 Union?或者我是否需要为我的班级提供更多的功能依赖 - 我不太了解它们。
我正在使用 purs 版本 0.12.0
由于使用了 Row.Cons,Var 的实例已经是多态的(或者技术上是开放的?),即
eval (Var :: Var "a" Int) :: forall r. { "a" :: Int | r } -> Int
Run Code Online (Sandbox Code Playgroud)
然后我们所要做的就是使用相同的记录进行左右评估,并且类型系统可以推断两者的组合而不需要并集:
instance evalAdd ::
( Component a Int r
, Component b Int r
) => Component (Add a b) Int r where
eval (Add a b) r = (eval a r) + (eval b r)
Run Code Online (Sandbox Code Playgroud)
当不使用类型类时,这一点更加明显:
> f r = r.foo :: Int
> g r = r.bar :: Int
> :t f
forall r. { foo :: Int | r } -> Int
> :t g
forall r. { bar :: Int | r } -> Int
> fg r = (f r) + (g r)
> :t fg
forall r. { foo :: Int, bar :: Int | r } -> Int
Run Code Online (Sandbox Code Playgroud)
我认为与 @erisco 相比,这种方法的缺点是开放行必须位于 Var 等实例的定义中,而不是 eval 的定义中?它也不是强制执行的,因此如果组件不使用开放行,则诸如 Add 之类的组合器将不再起作用。
好处是不需要 RProxies,除非 eriscos 实现实际上不需要它们,我没有检查过。
更新:
我找到了一种要求关闭 eval 实例的方法,但这使得它变得非常丑陋,利用了来自purescript-record-extra 的pick。
我不太确定为什么这会比上面的选项更好,感觉就像我只是重新实现行多态性
eval (Var :: Var "a" Int) :: forall r. { "a" :: Int | r } -> Int
Run Code Online (Sandbox Code Playgroud)