Предположим, у меня есть очень простой способ генерации потока из функции перехода состояния:
machine :: (s -> s) -> (s -> a) -> s -> [a]
machine next proj s0 = proj <$> iterate next s0
В какой-то момент я хотел бы использовать эту функцию, не заботясь о точном выборе s
. Допустим, у меня есть тип для моего состояния, который имеет параметр типа:
data Foo a = Foo1 Int | Foo2 a
, и у меня есть функция перехода состояния, которая имеет в виду a
:
{-# LANGUAGE ExistentialQuantification #-}
data Step = forall a. MkStep (Foo a -> Foo a)
step :: Step
step = _
I осторожно наблюдаю мое состояние в параметре типа-агности c way:
observe :: forall a. Foo a -> Int
observe (Foo1 x) = x
observe _ = 0
Мое начальное состояние также является параметром-агности типа c:
initial :: forall a. Foo a
initial = Foo1 0
Учитывая все Я надеялся, что в этих благоприятных условиях я смогу упаковать состояние initial
, переход step
и observe
, так как оба экземпляра initial
и observe
могут быть созданы в соответствии с выбором функции step
a
, но без игры в кости:
test :: [Int]
test = let MkStep next = step in machine next observe initial
• Couldn't match expected type ‘p’
with actual type ‘Foo a -> Foo a’
because type variable ‘a’ would escape its scope
This (rigid, skolem) type variable is bound by
a pattern with constructor:
MkStep :: forall a. (Foo a -> Foo a) -> Step,
in a pattern binding
at StateMachine.hs:21:12-22
• In the pattern: MkStep next
In a pattern binding: MkStep next = step
In the expression:
let MkStep next = step in machine next observe initial ```
Есть ли способ достичь этого без изменения типа machine
? [1]
Интересно, что использование Step
в качестве параметра работает:
test2 :: Step -> [Int]
test2 (MkStep next) = machine next observe initial
Но я бы не стал переписывать свой код в этом направлении.
[1] Здесь я использую списки , но для моего реального случая использования machine
- это комбинатор сигналов Cla sh из стандартной библиотеки.