我正在写自己的LISP,基于48小时内自己写一个方案。 (代码是here。)作为最后一个练习,我想实现宏。考虑到我将表达式表示为不可变数据类型列表,如何做到这一点。这可以简单地在LISP本身中完成,还是必须在Haskell中实现某些功能?
我目前的实现是用Haskell编写的,几乎是这样的:
表达式在Haskell中表示如下:
data Expr
= Sym String
| List [Expr]
| Num Int
| Str String
| Bool Bool
| Func Env [String] Expr
| Prim ([Expr] -> ErrorOr Expr)
| Action ([Expr] -> IOErrorOr Expr)
好吧,现在到了真正的问题。宏不会评估其参数,而是通过在表单中“放置”参数来转换为表达式。返回可以作为带引号的列表进行评估或返回的有效表达式。我想通过一个特殊的评估函数来实现它,它只评估宏形式的符号。如何才能实现这一点,这是我对问题的理解。正确的解决方案感觉我应该“简单地”通过用参数替换其中的符号来修改表单,但由于Haskell的不变性,这是不可能的。
所以,Clojure似乎已经在Lisp本身实现了宏。我无法解释Clojure的解决方案,但如果可以做到这一点,感觉就像在Haskell中做起来要容易一些。我不知道macroexpand1(macroexpand调用)做了什么,它是否从Clojure的实现中调用了一些函数?如果是这样,那么我仍然需要在Haskell中实现它。
如果我们看看如何评估函数:
eval env (List (op:args)) = do
func <- eval env op
args <- mapM (eval env) args
apply func args
apply :: Expr -> [Expr] -> IOErrorOr Expr
apply (Prim func) args = liftToIO $ func args
apply (Action func) args = func args
apply (Func env params form) args =
case length params == length args of
True -> (liftIO $ bind env $ zip params args)
>>= flip eval form
False -> throwError . NumArgs . toInteger $ length params
apply _ _ = error "apply"
所以,如果我想实现一个宏系统,那么我可能会删除参数的评估部分,然后将宏参数绑定到它的参数,并有一个特殊的eval,它只计算表单中的每个符号,返回一个将新参数放入其中的新表单。这是我无法实现的,我甚至不确定逻辑是否正确。
我理解这个问题非常广泛,可能更简单地问“我如何在用Haskell编写的LISP实现中实现宏系统
答案 0 :(得分:3)
不,在Lisp本身中无法实现宏,这是他们的全部观点。作为加载/编译/处理给定表达式的一部分,您必须根据其定义宏扩展每个宏调用。
您必须更改eval
实现以在未评估的参数上调用宏,并将结果反馈回eval
(而不是apply
,就像处理正常的函数应用程序一样)。正如评论中sepp2k
所建议的那样,您将宏表示为Func...
表达式,但将它们保存在一个单独的环境中,只存储宏。
答案 1 :(得分:2)
您可以尝试阅读计算机程序的结构和实现中的解释器实现。您显示的eval
功能仅适用于默认评估规则,而不适用于本书所称的特殊表单。
正常的Lisp eval
函数看起来更像是这样:
eval env expr@(List _)
| isSpecialForm env expr = evalSpecial env expr
| otherwise = evalApplication env expr
evalApplication env (op:args) = do
func <- eval env op
args <- mapM (eval env) args
apply func args
evalSpecial env expr@(List (op:args))
| isMacro env op = eval env (macroExpand env expr)
| otherwise = case op of
"lambda" -> ...
"if" -> ...
-- etc.
答案 2 :(得分:0)
您不需要特殊版本的apply
。只需调用常规apply
而不评估参数,然后调用eval
apply
返回的表达式。