【问题标题】:Finite State Transducers in Haskell?Haskell中的有限状态传感器?
【发布时间】:2015-01-17 07:13:41
【问题描述】:

我一直想知道是否有一种方法可以在 Haskell 中以惯用的方式定义和使用 finite state transducers

您可以将 FST 当作 generators(它生成 {x1,x2} 类型的输出)或 recognizers(给定 {x1,x2 类型的输入) } 如果它属于有理关系,它会识别它),或者作为翻译器(给定一个输入磁带,它将它翻译成一个输出磁带)。表示会根据方法而改变吗?

是否也可以通过指定重写规则来对 FST 进行建模?例如,创建一个 DSL 来对重写规则进行建模,然后创建一个函数 createFST :: [Rule] -> FST

我能找到的最接近的是 Kmett、Bjarnason 和 Cough 的 machines 库: https://hackage.haskell.org/package/machines

但我似乎无法意识到如何使用Machine 对 FST 进行建模。我认为正确的做法类似于他们定义 Moore 和 Mealy 机器的方式:将 FST 定义为不同的实体,但提供 Automaton 的实例以便能够将其用作机器。

我也找到了一些其他选项,但它们以一种直接的方式定义它(如在 https://hackage.haskell.org/package/fst 中)。这并没有让我很信服,因为我想知道是否有更好的方法来使用 Haskell 类型系统的优势(比如在 machines 库中如何定义 Moore 和 Mealy 机器)。

【问题讨论】:

  • 这个问题需要编辑才能成为 StackOverflow 的主题。明确要求图书馆或资源推荐是题外话,但描述特定问题并征求解决方案(这很可能涉及图书馆)是可以接受的。询问做某事的最佳方法通常过于宽泛或基于意见。要求一种方法来做某事仍然会得到高质量的答案,但对其优点的自以为是的讨论较少。

标签: haskell state-machine transducer


【解决方案1】:

Mealy 机器交替地从输入流a 中读取a 并将b 输出到输出流。它先读取,然后在每次读取后输出一次。

newtype Mealy a b = Mealy { runMealy :: a -> (b, Mealy a b) }

Moore 机器交替将b 输出到输出流,并从输入流中读取输入a。它以b 的输出开始,然后在每次输出后读取一次。

data Moore a b = Moore b (a -> Moore a b)

FST 要么从其输入读取、写入其输出,要么停止。它可以连续读取任意次数,也可以连续写入任意次数。

data FST a b
    = Read  (a -> FST a b)
    | Write (b,   FST a b)
    | Stop

相当于来自机器的FSTProcess。它的定义有点分散。为了简化讨论,我们暂时忘记Process,从内到外探索它。

基函数

为了描述Process 是什么,我们将首先注意到到目前为止所有三台机器中的一个模式。它们中的每一个都递归地引用自己的“下一步做什么”。我们将用任何类型next 替换“下一步做什么”。 Mealy 机器在将输入映射到输出的同时,还提供 next 机器运行。

newtype MealyF a b next = MealyF { runMealyF :: a -> (b, next) }

Moore 机器在输出并请求输入后,找出要运行的 next 机器。

data MooreF a b next = MooreF b (a -> next)

我们可以用同样的方式写FST。当我们从输入中Read 时,我们将根据输入找出要做什么next。当我们Write 到输出时,我们还将提供输出后要做什么next。当我们Stop 时,接下来无事可做。

data FSTF a b next
    = Read  (a -> next)
    | Write (b,   next)
    | Stop

这种消除显式递归的模式在 Haskell 代码中反复出现,通常称为“基函子”。在机器包中,基本函子是Step。与我们的代码相比,Step 将输出的类型变量重命名为 o,在 r 旁边做什么,读取到 Await,写入到 Yield

data Step k o r
  = forall t. Await (t -> r) (k t) r
  | Yield o r
  | Stop

Awaiting 比Read 稍微复杂一点,因为Machine 可以从多个来源读取。对于只能从单个源读取的Processes,k 是应用于特定类型的Is,这是对第二种类型Is 第一种类型的证明。对于Process 读取输入ak 将是Is a

data Step (Is a) o r
  = forall t. Await (t -> r) (Is a t) r
  | Yield o r
  | Stop

存在量化forall t.implementation detail for dealing with Sources。在见证了a ~ t之后就变成这样了。

data Step (Is a) o r
  = forall t ~ a. Await (t -> r) Refl r
  | Yield o r
  | Stop

如果我们将ta 统一起来并删除始终相同的Refl 构造函数,这看起来就像我们的FSTF

data Step (Is a) o r
  = Await (a -> r) r
  | Yield  o    r
  | Stop

Await 中额外的r 是在没有更多输入时下一步要做什么。

机器变压器`MachineT`

机器变压器MachineT 使Step 看起来几乎像我们的FST。它说,“在某个 monad m 上运行的机器是在该 monad 中执行的操作以获得下一个 Step。每个步骤之后要做的 next 是另一个 MachineT。”

newtype MachineT m k o = MachineT { runMachineT :: m (Step k o (MachineT m k o)) }

总的来说,专门针对我们的类型,这看起来像

newtype MachineT m (Is a) o = 
    MachineT m (
        Await (a -> MachineT m (Is a) o) (MachineT m (Is a) o)
      | Yield  o   (MachineT m (Is a) o)
      | Stop
    )

Machine 是一个纯粹的MachineT

type Machine k o = forall m. Monad m => MachineT m k o

对所有Monads m 的通用量化是另一种说法,即计算不需要来自底层Monad 的任何东西。这可以通过将Identity 替换为m 来查看。

type Machine k o = 
    MachineT Identity (
        Await (a -> MachineT Identity k o) (MachineT Identity k o)
      | Yield  o   (MachineT Identity k o)
      | Stop
    )

进程

ProcessProcessTMachineMachineT,仅读取单一类型的输入 aIs a

type Process    a b = Machine    (Is a) b
type ProcessT m a b = MachineT m (Is a) b

Process 在删除所有始终相同的中间构造函数后具有以下结构。这个结构和我们的FST 完全一样,只是它在没有更多输入的情况下增加了“下一步做什么”。

type Process a b =
    Await (a -> Process a b) (Process a b)
  | Yield  b   (Process a b)
  | Stop

ProcessT 变体有一个 m 包裹在其周围,以便它可以在每个步骤中在 monad 中起作用。

Process 模拟状态传感器。

【讨论】:

  • 很棒的machines 教程,谢谢。很长一段时间以来,我一直想尝试理解那个库。我想知道是否有办法以某种方式将此讨论附加到官方文档中。
  • 太棒了。使用 machines 库中的 Moore 和 Mealy 机器,您可以通过使用 unfoldMooreunfoldMealy 展开显式状态来创建机器。这些函数对定义 Moore/Mealy 机器的转换函数进行建模,因此您仍然可以使用它们以传统方式构建机器(定义状态,然后定义转换和输出函数)。是否可以为 FST 创建类似的功能(即Process)?
  • @gonzaw 这将是一个很好的后续问题。这个问题是关于如何在 Haskell 中对有限状态机进行建模,这个问题是关于如何使用机器库专门unfoldProcess。如果你能提供一个具体的问题,这个问题会更好,比如如何从data State = RepeatNext Word8 | SayAgain Word8 Word8step (RepeatNext n) = Await (x -> SayAgain n x) Stop; step (SayAgain 0 _) = Await (n -> RepeatNext n) Stop; step (SayAgain n x) = Yield x (SayAgain (n-1) x)展开Process
  • 好的。我将首先尝试玩弄Process 来尝试看看我是否能弄明白。如果没有,我会提出后续问题。到时候我会把它标记为已回答。
猜你喜欢
  • 2011-05-04
  • 1970-01-01
  • 1970-01-01
  • 2011-01-14
  • 2016-08-20
  • 1970-01-01
  • 2017-04-06
  • 2018-02-18
  • 1970-01-01
相关资源
最近更新 更多