我是Haskell新手,我正在尝试编写一个解析器来评估一组简单的Haskell表达式。但是,当我事先不知道它们是什么类型时,我遇到了功能上的困难。
例如,假设我知道要解析的字符串求值为整数。以下是字符串可能的几种可能性。
"succ 4"
"head [5,6,7]"
"sum [1,3,1]"
"length [a,b,c,d,e]"
"pred $ sum $ take 3 $ iterate succ 1"
天真的我最想做的事就是像这样的过程。
解析函数pred
,从它具有类型(Enum a) => a -> a
的事实推断出,并且我的字符串表示一个整数,其余的字符串(在美元之后)仍代表一个整数。
解析函数sum
并推断出我现在正在尝试评估整数列表。
解析函数take
并推断出我正在寻找一个整数和一个整数列表。
解析3并推断出其余字符串应该是整数列表。
解析iterate
并推断出我正在寻找类型为Int -> Int
的函数和一个整数。
解析succ
和1
。
执行评估。
问题在于,在过程的每个阶段,我遇到的下一个对象有许多可能的类型,如示例所示。所以我不能写一些通用的解析器来拉出一个函数并留下一个表示其参数(或参数)的字符串。但是如果我必须为每种可能的函数编写单独的解析器,那么它将很快成为一场噩梦,特别是当我必须查看多个变量的函数时。
我意识到它并不那么糟糕,因为许多功能都是针对几种不同的类型定义的。但是,例如,如果我说“如果字符串以”succ“开头然后将其拉出并将succ应用于对字符串其余部分的评估”,那么它会抱怨我应该指定我正在处理Enum
类中的类型。但是当我处理那种不属于那个类的类型时,我会遇到麻烦。
完全披露:我在Graham Hutton的书中使用简单的解析器来构建我想要构建的东西。所以也许答案是有更多高级解析器可以解决这类问题而我应该使用它。
答案 0 :(得分:3)
正如评论中指出的那样,编译器和口译员通常会在几次传递中工作,并且不会尝试在每次传球中做太多工作。
通常解析是一次传递,也许是在lexing之前。接下来是类型检查,然后是编译器转换为目标语言(通常是几次传递),或者是解释器,评估。对于快速而肮脏的实验或动态语言,您可以省略类型检查。
您应该首先定义一个抽象语法树来表示已解析的表达式 - Haskell代数数据类型是理想的。例如,您的"succ 4"
可能会解析为Apply (Symbol "succ") (Int 4)
之类的字词。您只需要本地信息来生成此信息,而不是周围值的类型 - 例如4
转换为Int 4
只是因为它是数字而非符号。
然后,您可以定义一个类型检查器来处理语法树,或者直接跳转到评估它并检查类型是否有意义。正确地对像Haskell这样的语言进行类型检查并不简单 - 如果您真的想要这样做,那么经典论文"Typing Haskell in Haskell"可能是最好的起点。