用Haskell编写Haskell解释器


90

一个经典的编程练习是用Lisp / Scheme编写Lisp / Scheme解释器。可以利用完整语言的功能为该语言的子集生成解释器。

Haskell有类似的练习吗?我想使用Haskell作为引擎来实现Haskell的子集。当然可以做到,但是有没有在线资源可供参考?


这是背景故事。

我正在探索使用Haskell作为语言来探索我正在教授的“ 离散结构”课程中的某些概念的想法。在本学期,我选择了米兰达语,这是一种较小的语言,启发了Haskell。Miranda完成了大约90%的工作,但是Haskell完成了大约2000%的工作。:)

所以我的想法是创建一种语言,该语言具有我想要的Haskell的功能,而不允许其他所有功能。随着学生的进步,一旦他们掌握了基础知识,我就可以有选择地“打开”各种功能。

教学“语言水平”已成功用于教授JavaScheme。通过限制他们可以做的事情,可以防止他们在他们仍在掌握要教的语法和概念的同时向自己开枪。并且您可以提供更好的错误消息。


我有一个WIP Haskell方言,它以Haskell中的键入Haskell为基础实现。chrisdone.com/toys/duet-delta上有它的演示。它尚未准备好公开开源发行,但如果有兴趣,我可以与您共享源。
Christopher Done

Answers:


76

我爱你的目标,但这是一项艰巨的工作。一些提示:

  • 我已经在GHC上工作,您不希望有任何来源。 Hugs是一个更简单,更简洁的实现,但是不幸的是它在C语言中。

  • 这只是个难题,但是马克·琼斯(Mark Jones)在Haskell撰写了一篇名为Typing Haskell的精美论文这将是您前端的一个很好的起点。

祝好运!为Haskell识别语言水平以及课堂上的支持证据,对社区将大有裨益,而且绝对是可发布的结果!


2
我想知道关于GHC的评论是否仍然准确。GHC很复杂,但是有据可查。特别是,内部组件Notes有助于理解底层细节,“ 开源应用程序体系结构”中有关GHC章节提供了出色的高层概述。
2014年

37

有完整的Haskell解析器:http : //hackage.haskell.org/package/haskell-src-exts

解析后,剥离或禁止某些操作很容易。我这样做是为了让tryhaskell.org禁止导入语句,以支持顶级定义等。

只需解析模块:

parseModule :: String -> ParseResult Module

然后,您有一个模块的AST:

Module SrcLoc ModuleName [ModulePragma] (Maybe WarningText) (Maybe [ExportSpec]) [ImportDecl] [Decl]    

Decl类型很广泛:http : //hackage.haskell.org/packages/archive/haskell-src-exts/1.9.0/doc/html/Language-Haskell-Exts-Syntax.html#t%3ADecl

您需要做的就是定义一个白名单-可以使用哪些声明,导入,符号,语法,然后遍历AST并在您不希望它们知道的任何内容上抛出“解析错误”。您可以使用附加到AST中每个节点的SrcLoc值:

data SrcLoc = SrcLoc
     { srcFilename :: String
     , srcLine :: Int
     , srcColumn :: Int
     }

无需重新实现Haskell。如果要提供更友好的编译错误,只需解析代码,对其进行过滤,将其发送给编译器,然后解析编译器输出。如果它是“无法与预期类型匹配,则不能与推断的类型匹配a -> b”,则说明该函数的参数可能太少。

除非您真的想花时间从头开始实施Haskell或与Hugs的内部搞混,或者有些笨拙的实施,否则我认为您应该只过滤传递给GHC的内容。这样,如果您的学生想使用自己的代码库,然后继续进行下一步,并编写一些真正成熟的Haskell代码,则过渡是透明的。


24

您想从头开始构建您的口译员吗?首先要实现一种更简单的函数式语言,例如lambda演算或lisp变体。对于后者,有一本相当不错的Wikibook,名为《48小时内编写自己的方案》,对解析和解释技术进行了冷静而务实的介绍。

手工解释Haskell会复杂得多,因为您将不得不处理非常复杂的功能,例如类型类,功能非常强大的类型系统(类型推断!)和惰性求值(归约技术)。

因此,您应该定义一个可处理的Haskell子集,然后逐步扩展Scheme示例。

加成:

请注意,在Haskell中,您可以完全使用解释器API(至少在GHC下),包括解析器,编译器,当然还有解释器。

要使用的包是hint(Language.Haskell。*)。不幸的是,我既没有找到关于此的在线教程,也没有亲自尝试过,但是看起来很有希望。


12
请注意,类型推断实际上是一种非常简单的20-30行算法。简单而美丽。惰性评估也不是很难编码。我想说的困难在于疯狂的语法,模式匹配以及语言中的大量内容。
Claudiu

有趣-您可以为类型推断算法发布链接吗?
Dario

5
是的,请查看这本免费书籍-cs.brown.edu/~sk/Publications/Books/ProgLangs/2007-04-26-,该书位于第273页(pdf的289页)。alg伪代码位于P296上。
Claudiu

1
功能编程语言的实现 ”中还提供了一个(the?)类型推断/检查算法的实现
Phil Armstrong,

1
但是,使用类型类进行类型推断并不简单。
克里斯托弗·迪克

20

创建一种语言,该语言恰好具有我想要的Haskell功能,并禁止其他所有功能。随着学生的进步,一旦他们掌握了基础知识,我就可以有选择地“打开”各种功能。

我建议一个更简单的解决方案(如减少工作量)。与其创建一个可以关闭功能的Haskell实现,不如将Haskell编译器包装到一个程序中,该程序首先检查代码是否使用了您不允许使用的任何功能,然后使用现成的编译器对其进行编译。

这将与HLint相似(也有点相反):

HLint(以前是Haskell博士)阅读Haskell程序,并提出了希望使其更易于阅读的更改。HLint还可以轻松禁用不需要的建议,并添加您自己的自定义建议。

  • 实施自己的HLint“建议”以不使用您不允许的功能
  • 禁用所有标准HLint建议。
  • 第一步,使包装程序运行修改后的HLint
  • 将HLint建议视为错误。也就是说,如果HLint被“投诉”,则程序不会进入编译阶段


6

EHC系列编译器可能是最好的选择:它正在积极开发并且似乎正是您想要的-一系列小型lambda结石编译器/解释器最终在Haskell '98中问世。

但是,您也可以查看Pierce的Types和Programming Languages中开发的各种语言,或Helium解释器(面向学生的残废Haskell,http://en.wikipedia.org/wiki/Helium_(Haskell )。


6

如果您正在寻找易于实现的Haskell子集,则可以取消类型类和类型检查。如果没有类型类,则不需要类型推断即可评估Haskell代码。

我为Code Golf挑战编写了一个自编译的Haskell子集编译器。它在输入上使用Haskell子集代码,并在输出上生成C代码。抱歉,没有可用的可读性更高的版本;在使它自动编译的过程中,我手工提出了嵌套定义。

对于有兴趣为Haskell的子集实现解释器的学生,我建议从以下功能入手:

  • 懒惰的评估。如果口译员在Haskell中,则可能无需为此做任何事情。

  • 具有模式匹配的参数和防护的函数定义。只担心变量,缺点,无和_模式。

  • 简单表达式语法:

    • 整数文字

    • 字符字面量

    • [] (零)

    • 功能应用程序(左关联)

    • 中缀:(缺点,正确关联)

    • 插入语

    • 变量名

    • 功能名称

更具体地说,编写一个可以运行此程序的解释器:

-- tail :: [a] -> [a]
tail (_:xs) = xs

-- append :: [a] -> [a] -> [a]
append []     ys = ys
append (x:xs) ys = x : append xs ys

-- zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f (a:as) (b:bs) = f a b : zipWith f as bs
zipWith _ _      _      = []

-- showList :: (a -> String) -> [a] -> String
showList _    []     = '[' : ']' : []
showList show (x:xs) = '[' : append (show x) (showItems show xs)

-- showItems :: (a -> String) -> [a] -> String
showItems show []     = ']' : []
showItems show (x:xs) = ',' : append (show x) (showItems show xs)

-- fibs :: [Int]
fibs = 0 : 1 : zipWith add fibs (tail fibs)

-- main :: String
main = showList showInt (take 40 fibs)

类型检查是Haskell的关键功能。但是,从一无所有到类型检查Haskell编译器非常困难。如果您从编写上述解释器开始,那么向其添加类型检查应该就不那么困难了。


“懒惰的评估。如果口译员在Haskell,您可能不需要为此做任何事情。” 这可能不正确。有关在Haskell中实现惰性解释器的更多信息,请参见haskell.org/wikiupload/0/0a/TMR-Issue10.pdf中 Naylor的文章。
Jared Updike 2014年


3

可能是一个好主意-在Haskell中制作NetLogo的小版本。是微小的翻译。


链接已死。此内容还有可能存在于其他地方吗?我很好奇...
Nicolas Payette

嗯,那是一篇博客文章,我不知道要使用什么关键字来搜索它。一个很好的教训,当提供链接时包括更多实质性信息...
Claudiu

1
Google搜索“ netlogo haskell”时出现了此问题。无论如何,没什么大不了的。谢谢!
Nicolas Payette



2

有人告诉我Idris有一个相当紧凑的解析器,不确定它是否真的适合修改,但是它是用Haskell编写的。


2

安德烈·鲍尔(Andrej Bauer)的“ 编程语言动物园 ”对纯函数式编程语言进行了小规模实施,这种语言有时被俗称为“ minihaskell”。它大约有700行OCaml,因此非常易于消化。

该网站还包含ML风格,Prolog风格和OO编程语言的玩具版本。


1

您难道认为,与从头开始编写自己的Haskell解释器相比,采用GHC来源并剔除不需要的内容会更容易吗?一般来说,应该有很多更轻松参与消除功能,而不是创建/加入的功能。

无论如何,GHC都是用Haskell编写的,因此从技术上讲,这与您用Haskell编写的Haskell解释器的问题有关。

使整个内容静态链接,然后仅分发您自定义的GHCi,可能并不难,这样学生就可以不加载其他Haskell源模块。至于阻止它们加载其他Haskell对象文件需要花费多少工作,我不知道。如果您的班级中有很多骗子,您可能也想禁用FFI :)


1
这听起来并不容易,因为许多功能依赖于其他功能。但是OP可能只是不想导入Prelude,而是提供自己的。您看到的大多数Haskell是正常功能,而不是运行时的特定功能。(当然,很多都是。)
jrockway

0

LISP解释器之所以如此之多,是因为LISP基本上是JSON的前身:一种用于编码数据的简单格式。这使得前端部分非常易于处理。相比之下,Haskell(尤其是语言扩展)不是最容易解析的语言。这些是听起来很棘手的语法结构:

  • 具有可配置优先级,关联性和固定性的运算符,
  • 嵌套评论
  • 布局规则
  • 模式语法
  • do-阻止和修改单子代码

在完成“编译器构建”课程后,学生可能会解决所有这些问题,除了操作员以外,但它们的重点将转移到Haskell的实际工作方式上。除此之外,您可能不希望直接实现Haskell的所有语法构造,而要实现传递以摆脱它们。这将我们带到了问题的字面意义上,完全出于本意。

我的建议是为Core而不是完整的Haskell 实现类型检查和解释器。这两项任务本身已经非常复杂。尽管仍然是一种强类型化的功能语言,但这种语言在优化和代码生成方面却不那么复杂。但是,它仍然独立于底层计算机。因此,GHC将其用作中介语言,并将Haskell的大多数语法构造转换为该语言。

此外,您不应回避使用GHC(或其他编译器)的前端。我不认为这是作弊,因为自定义LISP使用主机LISP系统的解析器(至少在引导过程中)。清理代码Core片段并将其与原始代码一起呈现给学生,应该使您能够概述前端的功能以及为什么最好不要重新实现它。

以下是CoreGHC中使用的文档的一些链接:

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.