Я придерживался того, что изложил во втором комментарии к вопросу.Проверка типов успешно убедила меня, что к этому должен быть иной подход.Если легко написать «пошаговый» интерпретатор для более простого типа данных, а индексированные типы данных усложняют его, то почему бы не определить run
с некоторым абстрактным типом данных, который позволил бы нам создать «простую» типизированную структуру, более легкую для интерпретации?
Подведем итоги:
data State = StateA | StateB
data Indexed : (ty : Type) -> State -> (ty -> State) -> Type where
Req : Indexed String StateA (const StateA)
Pure : (res : a) -> Indexed a (state_fn res) state_fn
(>>=) : Indexed a state1 state2_fn
-> ((res : a) -> Indexed b (state2_fn res) state3_fn)
-> Indexed b state1 state3_fn
Теперь давайте определим контекст выполнения и функцию run
, которая будет работать в этом контексте.run
даст нам окончательное значение, но сделает это с помощью некоторого абстрактного EventCtx
, потому что все, что нам нужно, это получить значения из внешних событий, и нам все равно, как вычисления будут обрабатывать их:
namespace EventType
data EventType = Req
data Event = Resp String
-- rename: eventType? what about EventType then :) ?
payloadType : EventType -> Type
payloadType EventType.Req = String
interface Monad m => EventCtx (m : Type -> Type) where
fromEvent : (et : EventType) -> m (payloadType et)
run : EventCtx m => Indexed a s1 s2_fn -> m a
run Req = fromEvent EventType.Req
run (Pure res) = pure res
run (x >>= f) = do
x' <- run x
run (f x')
run
теперь просто стандартное дело, ууу:)
Хорошо, но давайте проверим, как мы все еще можем запустить его шаг за шагом, с более простым типом, чтобымы можем где-то хранить промежуточные состояния (ожидая, когда произойдут события):
namespace SteppedRunner
data Stepped : Type -> Type where
Await : (et : EventType) -> Stepped (payloadType et)
Pure : a -> Stepped a
(>>=) : Stepped a -> (a -> Stepped b) -> Stepped b
Functor Stepped where
map f x = x >>= (\v => Pure (f v))
Applicative Stepped where
pure = Pure
(<*>) f a = f >>= (\f' =>
a >>= (\a' =>
Pure (f' a')))
Monad Stepped where
(>>=) x f = x >>= f
Это опять-таки довольно стандартно, мы получили более простой тип, облегчающий нашу интерпретацию и освобождающий нас от некоторых тяжелых-duty types.
Нам также нужна реализация нашего абстрактного EventCtx
, чтобы мы могли использовать run
, чтобы превратить наши Indexed
значения в Stepped
one:
EventCtx Stepped where
fromEvent = Await
Теперь наша функция для выполнения шага, заданного текущего состояния и события:
step : Stepped a -> Event -> Either (Stepped a) a
step s e = fst (step' s (Just e))
where
step' : Stepped a -> Maybe Event -> (Either (Stepped a) a, Maybe Event)
step' (Await Req) (Just (Resp s)) = (Right s, Nothing) -- event consumed
step' s@(Await _) _ = (Left s, Nothing) -- unmatched event (consumed)
step' (Pure x) e = (Right x, e)
step' (x >>= f) e = let (res, e') = step' x e
in case res of
Left x' => (Left (x' >>= f), e')
Right x => step' (f x) e'
Основная суть в том, что мы можем продолжать только тогда, когда мы связываем (>>=
) значение, и мы можем получить значение, когда у нас есть Await
и соответствующее событие.Отдых - это просто свертывание структуры, чтобы мы были готовы к другому значению события.
Некоторая программа тестирования:
test : Indexed String StateA (const StateA)
test = do
x <- Req
y <- do a <- Req
b <- Req
Pure (a ++ b)
Pure (x++ y)
Вот как мы переходим от исходного, индексированного типа данных к ступенчатому.:
s : Stepped String
s = run test
А теперь просто ради получения результата в тестовой среде:
steps : Stepped a -> List Event -> Either (Stepped a) a
steps s evs = foldl go (Left s) evs
where go (Right x) _ = Right x
go (Left s) e = step s e
Некоторые повторения:
λΠ> steps s [Resp "hoho", Resp "hehe", Resp "hihihi"]
Right "hohohehehihihi" : Either (Stepped String) String