使用的Haskell国家单一代的味道?
-
03-07-2019 - |
题
上帝我讨厌术语"代码闻",但是我想不出任何更加精确。
我设计一个高级别的语言和编译器 空白 在我的业余时间来了解编译器的建筑语言的设计和编程功能(compiler正在编写Haskell).
在代码生成阶段的编译器,我必须维护"国家"上下的数据作为我的历语法树。例如,当编制流程控制的发言,我需要产生独特的名称标跳到(签产生的一个计数器,是通过中,更新&返回,而旧值计数器必须永远不会被再次使用).另一个例子是,当我遇到过在线字符串中的语法树,他们需要永久性地转换成堆变量(在的空白,串是最好的储存堆).我目前正在环绕整个代码生成模块,在国家单处理这个问题。
我已经告诉我,编写一个编译器的问题以及适用于功能性的范例,但我觉得我设计这个一样我想设计它在C(你真的可以写C中的任何语言甚至Haskell w/国家元).
我想学习如何认为在哈斯克尔(相反,在功能模式)-不C Haskell语法。应该我真的尝试,以消除/减少使用国家单,或者是一个合法的功能性"的设计图案"?
解决方案
我想说的是,国家一般不是一种代码闻,只要它保存小型和很好的控制。
这意味着使用单元,例如国家、圣或定制的,或只是有一个数据结构中含有国家数据,通过周围的几个地方,不是一件坏事。(实际上,异是仅仅援助在这样做正是这个!) 然而,具有国家去所有的地方(是的,这意味着你,IO单!) 是一个不好的气味。
一个相当明显的例子,这是我的团队的工作对我们的项目 ICFP编程2009年比赛 (代码可在文件//饭桶。愤世嫉俗。net/haskell/icfp-比赛-2009年)。我们结束了与几个不同的模块部件:
- VM:虚拟机运行的模拟程序
- 控制器:几套不同的程序,读输出模拟器而产生的新的控制输入
- 方案:代解决方案文件的基础上的输出控制器
- 可视化工具:几套不同的程序,读输入和输出接口,并产生某种可视或日志的是怎么回事为该模拟的进展
这些都有其自己的国家,他们都互动,以各种方式通过输入和输出值的虚拟机。我们有几个不同的控制器和可视化工具,每一个都有自己的不同类型的状态。
这里的关键点是,内部任何特定国家局限于自己特定的模块,每个模块也不知道,即使存在国家对其他模块。任何特定的状态码和数据通常只有几十个线长,极少数的数据物品的状态。
所有这一切都是粘在一起,在一个小小功能对一个十几行它没有访问的内部结构的任何国家,而这仅仅是所谓的正确的事情在适当的顺序,因为它循环,通过模拟和通过非常有限的额外信息,每个模块(与模块之前的状态,当然)。
当国家是采用这样一种有限的方式和类型的系统是防止不经意地修改它,它是很容易处理。它是一个美女的Haskell,它可以让你这样做。
一个回答说,"不要使用单元" 从我的角度来看,这正是倒退。单元是一个控制结构,该结构除其他外,可以帮助你尽量减少量的代码,触摸的状态。如果你看一元的分析程序作为一个例子,国家的分析(即,文本正在分析如何远之一已经得到了它,任何警告,已经积累了,等等。) 必须通过每一组合使用的分析程序。然而只会有几个组合子,实际操作状态;任何其他用途之一,这些职能。这可以让你清楚地看到,在一个地方所有的少量的代码,可以改变的状态,并更容易理解它如何可以改变,再次使得更容易处理。
其他提示
我在Haskell中编写了多个编译器,状态monad是许多编译器问题的合理解决方案。但你想保持抽象 - 不要明显你使用monad。
这是格拉斯哥Haskell编译器的一个例子(我做了不写;我只是在几个边缘工作),我们在那里构建控制流图。以下是制作图表的基本方法:
empyGraph :: Graph
mkLabel :: Label -> Graph
mkAssignment :: Assignment -> Graph -- modify a register or memory
mkTransfer :: ControlTransfer -> Graph -- any control transfer
(<*>) :: Graph -> Graph -> Graph
但正如您所发现的,保持独特标签的供应充其量是乏味的,所以我们也提供这些功能:
withFreshLabel :: (Label -> Graph) -> Graph
mkIfThenElse :: (Label -> Label -> Graph) -- branch condition
-> Graph -- code in the 'then' branch
-> Graph -- code in the 'else' branch
-> Graph -- resulting if-then-else construct
整个 Graph
事物是一个抽象类型,翻译器只是以纯粹的功能方式快乐地构建图形,而不知道任何monadic正在发生。然后,当最终构造图形时,为了将其转换为代数数据类型,我们可以生成代码,我们为它提供一系列唯一标签,运行状态monad,并拉出数据结构。
状态monad隐藏在下面;虽然它没有暴露给客户端,但 Graph
的定义是这样的:
type Graph = RealGraph -> [Label] -> (RealGraph, [Label])
或更准确一点
type Graph = RealGraph -> State [Label] RealGraph
-- a Graph is a monadic function from a successor RealGraph to a new RealGraph
状态monad隐藏在一层抽象背后,它根本就没有臭!
使用AG,您可以将属性添加到语法树中。这些属性在合成和继承的属性中分开。
合成属性是您从语法树生成(或合成)的内容,可以是生成的代码,也可以是所有注释,或者您感兴趣的任何其他内容。
继承的属性输入到语法树,这可以是环境,也可以是代码生成期间使用的标签列表。
在乌特勒支大学,我们使用属性语法系统( UUAGC )编写编译器。这是一个预处理器,它从提供的 .ag
文件生成haskell代码( .hs
文件)。
虽然,如果你还在学习Haskell,那么也许现在不是开始学习另一层抽象的时候了。
在这种情况下,您可以手动编写属性语法为您生成的代码类型,例如:
data AbstractSyntax = Literal Int | Block AbstractSyntax
| Comment String AbstractSyntax
compile :: AbstractSyntax -> [Label] -> (Code, Comments)
compile (Literal x) _ = (generateCode x, [])
compile (Block ast) (l:ls) = let (code', comments) = compile ast ls
in (labelCode l code', comments)
compile (Comment s ast) ls = let (code, comments') = compile ast ls
in (code, s : comments')
generateCode :: Int -> Code
labelCode :: Label -> Code -> Code
我不认为使用状态Monad在用于建模状态时会产生代码气味。
如果你需要通过你的功能线程状态, 您可以明确地执行此操作,将状态作为参数并在每个函数中返回它。 State Monad提供了一个很好的抽象:它为你和你传递状态 提供了许多有用的功能来组合需要状态的功能。 在这种情况下,使用State Monad(或Applicatives)不是代码气味。
但是,如果您使用State Monad来模拟命令式编程 虽然功能性解决方案就足够了,但你只是让事情变得复杂。
一般情况下,你应尽量避免使用状态,但这并不总是实用的。 Applicative
使有效的代码看起来更好,更实用,尤其是树遍历代码可以从这种风格中受益。对于名称生成的问题,现在有一个相当不错的包可用:值供应。
好吧,不要使用monads。函数式编程的强大之处在于功能纯度及其重用。我的教授曾写过这篇论文,他是帮助建立Haskell的人之一。
该论文名为“为什么函数式编程很重要 “我建议你仔细阅读。这是一个很好的阅读。
让我们在这里注意术语。国家本身并不坏;功能语言有状态。什么是“代码气味”?当你发现自己想要分配变量值并改变它们时。
当然,Haskell状态monad就是出于这个原因 - 就像I / O一样,它让你在受限制的环境中做不安全和无功能的事情。
所以,是的,它可能是代码味道。