使用Haskell状态monad代码气味?

上帝我讨厌术语“代码气味”,但我不能想到更准确的东西。

我正在设计一个高级语言&编译器Whitespace在我的业余时间学习编译器构造,语言设计和函数编程(编译器正在Haskell编写)。

在编译器的代码生成阶段,当我遍历语法树时,我必须维护“状态”数据。例如,在编译流控制语句时,我需要为要跳转的标签生成唯一的名称(从传入,更新和返回的计数器生成的标签,并且计数器的旧值不能再次使用) 。另一个例子是当我在语法树中遇到内联字符串字面量时,它们需要永久转换为堆变量(在Whitespace中,字符串最好存储在堆上)。我目前包装的状态monad的整个代码生成模块来处理这个。

我被告知,编写一个编译器是一个非常适合函数范式的问题,但我发现我的设计方式与我在C语言中的设计方式(你真的可以用任何语言写C Haskell w / state monad)。

我想学习如何在Haskell(而不是在函数范式) – 不是在C与Haskell语法。我应该真的尝试消除/最小化使用状态monad,还是它是一个合法的功能“设计模式”?

最佳答案
我会说,一般状态不是一个代码的气味,只要它保持小和良好的控制。

这意味着使用monad,例如State,ST或定制的,或者只有一个包含状态数据的数据结构,你传递到几个地方,这不是一件坏事。 (实际上,monad只是帮助做这个!)但是,有状态遍布的地方(是的,这意味着你,IO monad!)是一种难闻的气味。

一个相当清楚的例子是当我的团队在我们的ICFP Programming Contest 2009进程(代码可以在git://git.cynic.net/haskell/icfp-contest-2009)。我们最终得到了几个不同的模块化部件:

> VM:运行仿真程序的虚拟机
>控制器:几组不同的例程,读取模拟器的输出并生成新的控制输入
>解决方案:基于控制器的输出生成解决方案文件
> Visualizers:几个不同的例程集,读取输入和输出端口,并生成某种可视化或日志,随着模拟进行

它们中的每一个具有其自己的状态,并且它们都以各种方式通过VM的输入和输出值进行交互。我们有几个不同的控制器和可视化器,每个都有自己不同的状态。

这里的关键是,任何特定状态的内部限制到它们自己的特定模块,并且每个模块甚至不知道其他模块的状态的存在。任何特定的有状态代码和数据集合通常只有几十行长,有一些状态下的数据项。

所有这一切都粘在一起的一个小功能,大约十几行,没有访问任何国家的内部,只是在正确的事情以正确的顺序,当它循环通过模拟,并通过了一个非常有限的每个模块的外部信息量(以及模块以前的状态,当然)。

当状态以这种有限的方式使用,并且类型系统阻止您无意中修改它时,它很容易处理。它是Haskell的一个美女,它让你这样做。

一个答案说,“不要使用单子。从我的角度来看,这是完全倒退。 Monads是一个控制结构,除其他外,可以帮助你最小化接触状态的代码量。如果你看看monadic解析器作为例子,解析的状态(即,正在解析的文本,它已经得到了多远,已经积累的任何警告等)必须运行通过解析器中使用的每个组合器。然而,只有几个组合器实际上直接操纵状态;任何其他使用这些几个功能之一。这让你可以清楚地看到,在一个地方,所有的小量的代码,可以改变状态,更容易推理如何可以改变,再次使它更容易处理。

转载注明原文:使用Haskell状态monad代码气味? - 代码日志