在非常简单的意义上,我有以下内容:
type Runtime a = {- More or less a StateT on top of an Either monad -}
-- The list of strings in Fn is a bunch of parameter names, the values of
-- which are pushed into the state of the runtime before executing the actual
-- function expr
data Expr = Num Int
| Str T.Text
| Fn [T.Text] (Runtime Expr)
| {- Bunch of other constructors -}
eval :: Expr -> Runtime Expr
parseExp :: Parser Expr
现在,在我决定为我的玩具语言准备一个准引号之前,我从来没有使用过模板Haskell,所以我承认我可能会遗漏一些明显的东西。
但无论如何,我开始摆弄它,跟着一些教程等等,基本上发现除了如何处理Fn
构造函数之外的一切都很容易。
在我在网上的信息搜索过程中,我发现了两种人们写表达式的一般方式:
Expr
数据类型成为TH的实例:Lift
并简单地[|引用|]解析导致的表达式Data
和Typeable
等同于Expr
,然后对同一个解析器结果应用dataToExpQ
在这两种情况下,我遇到了Runtime Expr
的并发症。对于第一种情况,问题是我无法弄清楚如何实施:
instance Lift Expr where
lift (Fn ps e) = [| Fn ps ...? |]
(我确实设法自己实现了Data.Text的实例)。
我认为真正的问题是我根本就不太了解TH,但到目前为止,没有多少教程或exmaples帮助我实现这一目标。
在第二种情况下,问题是Expr
要成为Data
的实例,还需要有
instance Data (StateT (...) (Either ...) Expr) where
-- Something
我的问题是,是否有一种简单的方法可以做到这一点?或许我应该重新考虑我的玩具语言的功能如何运作?
如果是后者,有关如何获得等效功能的任何建议而不在monad中运行它们?毕竟,这似乎是直观的解决方案,因为该语言的运行时环境需要状态和错误处理(这是我使用Either
)。
答案 0 :(得分:4)
您的Expr
数据类型不需要是可升级的,以便您为它构建一个quasiquoter,在这种情况下,实现Lift
实例是不可能的。原因是你不能“查看”Runtime Expr
值,因为StateT
值本质上是函数,并且没有通用的方法来找出函数的作用。
你需要做的是构建用手构建Expr
“的AST,即不使用[| |] -quoters(或者更具体地说,您可以使用[| |] -quotes来帮助您构建AST,但不能直接引用Expr
数据)。
所以从本质上讲,你需要一个类型为
的解析器parseExpQ :: Parser ExpQ
生成Exp,表示构建Expr
所需的Haskell代码。对于Fn
构造函数,您需要使用DoE
构造函数构建do-block或使用InfixE
和>>=
构建绑定链。
如果这听起来太复杂,那么另一种选择是将函数体表示为表达式(或语句列表,具体取决于您的玩具语言的语义)。即。
data Expr = Num Int
| Str T.Text
| Fn [T.Text] [Statement]
定义Statement
类型,以便可以将其解释为产生与之前使用StateT Either
相同的效果。