90

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

Haskell 有类似的练习吗?我想使用 Haskell 作为引擎来实现 Haskell 的一个子集。当然可以,但是有什么在线资源可以看吗?


这是背景故事。

我正在探索使用 Haskell 作为一种语言来探索我正在教授的离散结构课程中的一些概念的想法。这个学期我选择了Miranda,这是一种启发 Haskell 的小语言。Miranda 做了大约 90% 我希望它做的事情,但 Haskell 做了大约 2000%。:)

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

教学“语言级别”已成功用于教授JavaScheme。通过限制他们可以做的事情,您可以防止他们在他们仍在掌握您尝试教授的语法和概念时自欺欺人。您可以提供更好的错误消息。

4

15 回答 15

77

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

  • 我在 GHC 上工作过,你不想要任何部分的来源。 Hugs是一个更简单、更干净的实现,但不幸的是它是用 C 语言实现的。

  • 这是拼图的一小部分,但是 Mark Jones 写了一篇漂亮的论文,名为Typing Haskell in Haskell,这将是您前端的一个很好的起点。

祝你好运!确定 Haskell 的语言水平,以及来自课堂的支持性证据,将对社区大有裨益,而且绝对是可发表的结果!

于 2009-09-18T22:31:22.183 回答
39

有一个完整的 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 与推断的a -> b”,那么您知道它可能是函数的参数太少。

除非你真的真的想花时间从头开始实现 Haskell 或者搞乱 Hugs 的内部结构,或者一些愚蠢的实现,我认为你应该只过滤传递给 GHC 的内容。这样,如果您的学生想要使用他们的代码库并将其带到下一步并编写一些真正成熟的 Haskell 代码,那么过渡是透明的。

于 2010-07-17T18:09:46.613 回答
24

您想从头开始构建您的解释器吗?从实现更简单的函数式语言开始,例如 lambda 演算或 lisp 变体。对于后者,有一本非常不错的 wikibook,名为在 48 小时内为自己编写一个方案,对解析和解释技术进行了冷静而实用的介绍。

手动解释 Haskell 会复杂得多,因为您必须处理高度复杂的特性,例如类型类、极其强大的类型系统(类型推断!)和惰性求值(归约技术)。

所以你应该定义一个相当小的 Haskell 子集来使用,然后可能从逐步扩展 Scheme-example 开始。

添加:

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

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

于 2009-09-18T17:36:58.907 回答
20

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

我建议对这个问题采用更简单的(因为涉及的工作更少)解决方案。与其创建一个可以关闭功能的 Haskell 实现,不如用一个程序包装一个 Haskell 编译器,该程序首先检查代码没有使用您不允许的任何功能,然后使用现成的编译器对其进行编译。

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

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

  • 实施您自己的 HLint“建议”以不使用您不允许的功能
  • 禁用所有标准 HLint 建议。
  • 让您的包装器运行您修改后的 HLint 作为第一步
  • 将 HLint 建议视为错误。也就是说,如果 HLint “抱怨”,那么程序不会进入编译阶段
于 2009-09-18T21:31:07.370 回答
16

Baskell 是一个教学实现,http: //hackage.haskell.org/package/baskell

你可以从选择要实现的类型系统开始。这和 Scheme 的解释器一样复杂,http://hackage.haskell.org/package/thih

于 2009-09-19T16:20:20.997 回答
6

EHC 系列编译器可能是最好的选择:它正在积极开发,似乎正是您想要的——一系列小型 lambda 演算编译器/解释器,最终在 Haskell '98 中达到顶峰。

但是您也可以查看 Pierce 的类型和编程语言中开发的各种语言,或 Helium 解释器(一个供学生使用的残废的 Haskell http://en.wikipedia.org/wiki/Helium_(Haskell))。

于 2009-12-21T16:15:20.057 回答
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 编译器是非常困难的。如果您从为上述内容编写解释器开始,向其中添加类型检查应该不那么令人生畏。

于 2011-10-11T01:10:40.887 回答
3

您可能会查看具有 Haskell 解析器的Happy (Haskell 中的类似 yacc 的解析器)。

于 2009-09-18T17:30:11.730 回答
3

可能是个好主意——在 Haskell 中制作一个小版本的 NetLogo。是微型解释器。

于 2009-09-28T22:55:48.670 回答
2

看看是否会比标准的haskell成为更好的基础。

于 2009-09-18T21:35:36.257 回答
2

Uhc/Ehc 是一系列启用/禁用各种 Haskell 功能的编译器。 http://www.cs.uu.nl/wiki/Ehc/WebHome#What_is_UHC_And_EHC

于 2009-09-21T22:20:32.197 回答
2

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

于 2012-04-07T22:03:18.303 回答
2

Andrej Bauer 的编程语言动物园有一个纯函数式编程语言的小实现,有点厚颜无耻地命名为“minihaskell”。它大约有 700 行 OCaml,因此非常容易消化。

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

于 2012-05-12T10:25:28.753 回答
1

你不认为获取GHC 资源并去掉你不想要的东西会比从头开始编写你自己的 Haskell 解释器更容易吗?一般来说,与创建/添加功能相比,删除功能所涉及的工作量应该少得多

无论如何,GHC 都是用 Haskell 编写的,所以从技术上讲,这与您对用 Haskell 编写的 Haskell 解释器的问题保持一致。

将整个东西静态链接然后只分发您定制的 GHCi 可能不会太难,这样学生就无法加载其他 Haskell 源模块。至于阻止它们加载其他 Haskell 目标文件需要做多少工作,我不知道。如果您的班级中有很多作弊者,您可能也想禁用 FFI :)

于 2009-09-18T22:03:09.670 回答
0

之所以有这么多 LISP 解释器,是因为 LISP 基本上是 JSON 的前身:一种对数据进行编码的简单格式。这使得前端部分很容易处理。与此相比,Haskell,尤其是语言扩展,并不是最容易解析的语言。这些是一些听起来很难正确的句法结构:

  • 具有可配置优先级、关联性和固定性的运算符,
  • 嵌套评论
  • 布局规则
  • 模式句法
  • do- 对单子代码进行块和脱糖

这些中的每一个,除了可能的操作符,都可以在编译器构建课程之后由学生解决,但这会使焦点从 Haskell 的实际工作原理上转移开。除此之外,您可能不想直接实现 Haskell 的所有语法结构,而是实现 pass 以摆脱它们。这将我们带到了问题的字面核心,完全是双关语。

我的建议是实现类型检查和解释器,Core而不是完整的 Haskell。这两项任务本身已经相当复杂。这种语言虽然仍然是一种强类型的函数式语言,但在优化和代码生成方面处理起来并不复杂。但是,它仍然独立于底层机器。因此,GHC 使用它作为中介语言,并将 Haskell 的大多数语法结构翻译成它。

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

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

于 2018-06-14T12:55:35.703 回答