Purescript 行联合

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

Jos*_*ung 1

由于使用了 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)