用索引数据类型解释计算构建的另一种方法

时间:2018-10-13 20:21:42

标签: idris

在“使用Idris进行类型驱动的开发”一书中,有几个使用monadic数据类型编码“程序”的示例(导致使用索引数据类型进行有用的类型安全编码)。通常,每种这样的数据类型都可以在特定的上下文中运行(出于学习目的,大多数情况下为IO)。

我试图编写另一个“运行器”,该运行器不会在单子上下文中执行,而是在给定某些输入的情况下会执行一个step的功能-如果输入与当前程序状态匹配,我们d输入其值并继续,获取下一个程序状态。

如果数据类型没有索引,这很容易键入:

  data Prog : Type -> Type where
    Req : Prog String
    Pure : a -> Prog a
    (>>=) : Prog a -> (a -> Prog b) -> Prog b

  data Event = Resp String

  run : Prog a -> Event -> Prog a
  run Req (Resp s) = Pure s
  run (Pure x) _ = Pure x
  run (x >>= f) ev = let x' = run x ev
                    in case x' of
                          Pure v => f v
                          v => v >>= f

还有一个示例:

  prog : Prog String
  prog = do
    x <- Req
    y <- Req
    Pure (x ++ y)

  test : IO ()
  test = do
    -- this might doesn't look reasonable , but the point is that we could
    -- keep/pass program state around and execute it in whatever manner we wish
    -- in some environment
    let s1 = run prog (Resp "Hello, ")
    let s2 = run s1 (Resp "world")
    case s2 of
      Pure s => putStrLn s
      _ => putStrLn "Something wrong"

这一切似乎都可以正常工作,但是当对主要数据类型建立索引并以依赖类型的方式(取决于结果)跟踪其状态时,事情会变得复杂:

data State = StateA | StateB

data Event = Resp String

data Indexed : (ty : Type) -> State -> (ty -> State) -> Type where
  Req : Indexed String StateA (\res => case res of "frob" => StateB; _ => 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 : Indexed a s1 s2_fn -> Event -> Indexed a s3 s4_fn

不会削减它,因为调用者不会决定结果状态。这使我尝试用依赖对“隐藏”这些参数:

run : Indexed a s1 s2_fn -> Event -> (s3 ** s4_fn ** Indexed a s3 s4_fn)

的意思是“为我在特定状态下运行该程序,我不在乎结果状态(索引)将是什么。”

但是随后,Pure出现了问题:

run (Pure x) _ = (?noIdea ** ?noIdeaAsWell ** (Pure x))

所以也许我们还需要输入索引:

run : (s1 ** s2_fn ** Indexed a s1 s2_fn) -> Event -> (s3 ** s4_fn ** Indexed a s3 s4_fn)

,但是类型错误很快就变得太多了,大量的工作只是为了“重新创建”知道过渡的从属对的值(无论如何,过渡已经定义了)。这导致我以为自己走错了路。如何尝试编写这样的解释器?

1 个答案:

答案 0 :(得分:0)

我追求的是我在对该问题的第二条评论中概述的内容。类型检查器成功说服我应该采用其他方法。如果为更简单的数据类型编写“步骤”解释器很容易,而索引数据类型使它变得更难,那么为什么不为某个抽象数据类型定义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

这又是相当标准的,我们获得的是更简单的类型,使我们的解释更加容易,并使我们摆脱了一些重型类型。

我们还需要实现抽象EventCtx的实现,以便能够使用runIndexed的值转换为Stepped的值:< / p>

  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