经典的编程练习是在Lisp / Scheme中编写一个Lisp / Scheme解释器。可以利用完整语言的强大功能为该语言的子集生成解释器。
Haskell有类似的练习吗?我想使用Haskell作为引擎来实现Haskell的子集。当然可以完成,但有没有可供查看的在线资源?
<小时/> 这是背景故事。
我正在探索使用Haskell作为一种语言来探索我正在教授的Discrete Structures课程中的一些概念的想法。在本学期,我选择Miranda,这是一种启发Haskell的小语言。米兰达做了我想做的事情的90%左右,但哈斯克尔做了大约2000%。 :)
所以我的想法是创建一种具有我想要的Haskell功能的语言,并禁止其他所有功能。随着学生的进步,我可以在掌握基础知识后有选择地“开启”各种功能。
教学“语言水平”已成功用于教授Java和Scheme。通过限制他们可以做的事情,你可以防止他们在掌握你想要教授的语法和概念的同时在脚中射击。并且您可以提供更好的错误消息。
答案 0 :(得分:74)
我热爱你的目标,但这是一项艰巨的任务。一些提示:
我参与了GHC,你不需要任何部分资源。 Hugs是一个更简单,更简洁的实现,但不幸的是它在C中。
这是谜题的一小部分,但马克琼斯写了一篇名为Typing Haskell in Haskell的精美论文,这将是你前端的一个很好的起点。
答案 1 :(得分: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]
您需要做的就是定义一个白名单 - 声明,导入,符号,语法可用,然后走AST并在您不希望他们知道的任何内容上抛出“解析错误”但是您可以使用附加到AST中每个节点的SrcLoc值:
data SrcLoc = SrcLoc
{ srcFilename :: String
, srcLine :: Int
, srcColumn :: Int
}
没有必要重新实现Haskell。如果要提供更友好的编译错误,只需解析代码,过滤它,将其发送到编译器,然后解析编译器输出。如果它是“无法匹配预期类型a与推断的a -> b
”,那么你知道函数的参数可能太少了。
除非你真的真的想花时间从头开始实现Haskell或搞乱Hugs的内部,或者一些愚蠢的实现,否则我认为你应该只过滤传递给GHC的内容。这样,如果你的学生想要采用他们的代码库并将其带入下一步并编写一些真正完全成熟的Haskell代码,那么过渡是透明的。
答案 2 :(得分:24)
您想从头开始构建解释器吗?首先实现一个更简单的函数语言,如lambda演算或lisp变体。对于后者,有一个非常好的wikibook称为Write yourself a Scheme in 48 hours,它为解析和解释技术提供了一个很酷和实用的介绍。
手工解释Haskell将会复杂得多,因为你必须处理高度复杂的特性,如类型类,一个非常强大的类型系统(类型推理!)和懒惰评估(简化技术)。
所以你应该定义一个很小的Haskell子集,然后开始逐步扩展Scheme-example。
增加:
请注意,在Haskell中,您可以完全访问解释器API(至少在GHC下),包括解析器,编译器和解释器。
要使用的包是hint (Language.Haskell.*)。遗憾的是,我没有找到关于此的在线教程,也没有自己试过,但看起来很有希望。
答案 3 :(得分:20)
创建一种具有我想要的Haskell功能的语言,并禁止其他所有功能。随着学生的进步,我可以在掌握基础知识后有选择地“开启”各种功能。
我建议更简单(因为涉及的工作量较少)解决这个问题。您可以使用程序首先检查代码是否使用您不允许的任何功能,然后使用现成的编译器对其进行编译,而不是创建可以关闭功能的Haskell实现,而不是创建Haskell实现。 >
这类似于HLint(也有点相反):
HLint(以前是Haskell博士)读取Haskell程序并建议改变,希望它们更容易阅读。 HLint还可以轻松禁用不需要的建议,并添加您自己的自定义建议。
答案 4 :(得分:16)
Baskell是一个教学实现,http://hackage.haskell.org/package/baskell
您可以先选择要实施的类型系统。这与Scheme的解释器http://hackage.haskell.org/package/thih
一样复杂答案 5 :(得分:6)
EHC系列编译器可能是最好的选择:它是积极开发的,似乎正是你想要的 - 一系列小型lambda演算编译器/解释器最终在Haskell '98。
但是你也可以看看在Pierce的类型和编程语言中开发的各种语言,或者是Helium解释器(用于学生http://en.wikipedia.org/wiki/Helium_(Haskell)的残缺Haskell)。
答案 6 :(得分:6)
如果您正在寻找易于实现的Haskell子集,则可以取消类型类和类型检查。如果没有类型类,则不需要类型推断来评估Haskell代码。
我为Code Golf挑战写了self-compiling Haskell subset compiler。它在输入上获取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编译器是非常困难的。如果你开始为上面写一个解释器,那么添加类型检查应该不那么令人生畏。
答案 7 :(得分:3)
您可以查看具有Haskell解析器的Happy(Haskell中类似于yacc的解析器)。
答案 8 :(得分:3)
答案 9 :(得分:2)
看看helium是否会比标准的haskell更好地构建基础。
答案 10 :(得分:2)
Uhc / Ehc是一系列启用/禁用各种Haskell功能的编译器。 http://www.cs.uu.nl/wiki/Ehc/WebHome#What_is_UHC_And_EHC
答案 11 :(得分:2)
我被告知Idris有一个相当紧凑的解析器,不确定它是否真的适合更改,但它是用Haskell编写的。
答案 12 :(得分:2)
Andrej Bauer的Programming Language Zoo有一个小功能编程语言的小实现,有点厚颜无耻地命名为“minihaskell”。它大约有700行OCaml,因此很容易消化。
该网站还包含ML风格,Prolog风格和OO编程语言的玩具版本。
答案 13 :(得分:1)
你认为从头开始编写自己的Haskell解释器时,你认为把the GHC sources和删除你不想要的东西会更容易吗?一般来说,与创建/添加功能相比,删除功能所需的批次应该更少。
GHC无论如何都是用Haskell编写的,所以从技术上讲,这与你用Haskell编写的Haskell解释器的问题保持一致。
将整个事物静态链接起来然后只分发自定义的GHCi可能不会太难,这样学生就无法加载其他Haskell源模块。至于防止它们加载其他Haskell对象文件需要多少工作,我不知道。如果您的班级中有许多作弊者,您可能也想要禁用FFI:)
答案 14 :(得分:0)
LISP解释器有这么多的原因是LISP基本上是JSON的前身:一种简单的数据编码格式。这使得前端部分非常容易处理。与此相比,Haskell,尤其是语言扩展,并不是最容易解析的语言。 这些是一些听起来难以理解的语法结构:
do
- 阻止和贬低monadic代码除了操作员之外,每个人都可以在编译器构建课程之后由学生解决,但是它将把重点从Haskell的实际工作方式上移开。除此之外,您可能不希望直接实现Haskell的所有语法结构,而是实现传递来摆脱它们。这将我们带入了问题的文字核心,双关语是完全有意的。
我的建议是为Core
而不是完整的Haskell实现类型检查和解释器。这两项任务本身已经非常复杂。
这种语言虽然仍然是一种强类型的函数式语言,但在优化和代码生成方面处理起来并不复杂。
但是,它仍然独立于底层机器。
因此,GHC将其用作中间语言,并将Haskell的大多数语法结构转换为它。
此外,你不应回避使用GHC(或其他编译器)的前端。
我不认为这是作弊,因为自定义LISP使用主机LISP系统的解析器(至少在引导期间)。清理Core
个片段并将其与原始代码一起呈现给学生应该可以让您概述前端的功能,以及为什么不重新实现它。
以下是GHC中使用的Core
文档的一些链接: