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