Purecript Союз Строк

Я пытался разработать систему компонентов в Purescript, используя класс типов Component, который определяет функцию eval. Функция eval для может быть рекурсивно вызвана компонентом для каждого подкомпонента компонента, по сути, выбирая значения ввода.

Поскольку компоненты могут захотеть использовать значения времени выполнения, запись также передается в eval. Моя цель состоит в том, чтобы строки в аргументе Record верхнего уровня eval должны были включать все строки каждого подкомпонента. Это не слишком сложно для компонентов, которые не используют сами строки, но их отдельный подкомпонент использует, так как мы можем просто передать строки подкомпонентов компонентам. Это показано в 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

Весь приведенный выше код работает правильно. Однако, как только я пытаюсь представить компонент, который принимает несколько входных компонентов и объединяет их строки, я не могу заставить его работать. Например, при попытке использовать class Union от 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)

Произошла следующая ошибка:

  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

На самом деле, даже модифицируя evalInc Например, использование фиктивного объединения с пустой строкой приводит к аналогичной ошибке, например:

instance evalInc :: (Component a Int r, Union r () r1) 
                       => Component (Increment a) Int r1 where

Я неправильно использую Union? Или мне нужны дополнительные функциональные зависимости для моего класса - я их не очень хорошо понимаю.

Я использую Purs версии 0.12.0

0 ответов

r ∷ r3 но он используется там, где r1 а также r2 являются обязательными, поэтому существует несоответствие типов. Запись {a ∷ A, b ∷ B} не может быть дано где {a ∷ A} или же {b ∷ B} или же {} ожидается. Однако можно сказать так:

f ∷ ∀ s r. Row.Cons "a" A s r ⇒ Record r → A
f {a} = a

Прописью, f является полиморфной функцией для любой записи, содержащей метку "a" с типом A, Точно так же вы можете изменить eval на:

eval ∷ ∀ s r. Row.Union c s r ⇒ a → Record r → b

Прописью, eval является полиморфным для любой записи, которая содержит по крайней мере поля c, Это вводит двусмысленность типа, которую вы должны будете решить с помощью прокси.

eval ∷ ∀ proxy s r. Row.Union c s r ⇒ proxy c → a → Record r → b

Eval экземпляр Add становится:

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

Отсюда, r1 а также r2 стать двусмысленным, потому что они не определены из r3 в одиночестве. С учетом ограничений, s1 а также s2 также должно быть известно. Возможно, есть функциональная зависимость, которую вы можете добавить. Я не уверен, что подходит, потому что я не уверен, каковы цели программы, которую вы разрабатываете.

Поскольку экземпляр для Var уже полиморфен (или технически открыт?) Из-за использования Row.Cons, т.е.

eval (Var :: Var "a" Int) :: forall r. { "a" :: Int | r } -> Int

Тогда все, что нам нужно, это использовать одну и ту же запись для левой и правой оценки, и система типов может вывести комбинацию из двух, не требуя объединения:

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)

Это более очевидно, когда не используются классы типов:

> 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

Я думаю, что недостатком этого подхода по сравнению с @ erisco является то, что открытая строка должна быть в определении экземпляров типа Var, а не в определении eval? Это также не применяется, поэтому, если Компонент не использует открытые строки, комбинатор, такой как Добавить, больше не работает.

Преимуществом является отсутствие требования к RProxies, если только они на самом деле не нужны для реализации eriscos, я не проверял.

Обновить:

Я разработал способ требовать, чтобы eval-экземпляры были закрыты, но это делает его довольно уродливым, используя выбор из purescript-record-extra.

Я не совсем уверен, почему это было бы лучше по сравнению с вышеупомянутым вариантом, похоже, что я просто заново реализую полиморфизм строк

import Record.Extra (pick, class Keys)

...

instance evalVar :: 
  ( IsSymbol a
  , Row.Cons a b () r
  ) => Component (Var a b) b r where
  eval _ r = R.get (SProxy :: SProxy a) r

data Add a b = Add a b

evalp :: forall c b r r_sub r_sub_rl trash
   . Component c b r_sub
  => Row.Union r_sub trash r
  => RL.RowToList r_sub r_sub_rl
  => Keys r_sub_rl
  => c -> Record r -> b
evalp c r = eval c (pick r)

instance evalAdd :: 
  ( Component a Int r_a
  , Component b Int r_b
  , Row.Union r_a r_b r
  , Row.Nub r r_nub
  , Row.Union r_a trash_a r_nub
  , Row.Union r_b trash_b r_nub
  , RL.RowToList r_a r_a_rl
  , RL.RowToList r_b r_b_rl
  , Keys r_a_rl
  , Keys r_b_rl
  ) => Component (Add a b) Int r_nub where
  eval (Add a b) r = (evalp a r) + (evalp b r)

eval (Add (Var :: Var "a" Int) (Var :: Var "b" Int) ) :: { a :: Int , b :: Int } -> Int  
eval (Add (Var :: Var "a" Int) (Var :: Var "a" Int) ) :: { a :: Int } -> Int 
Другие вопросы по тегам