Haskell 如何在我的LISP中实现宏系统

Haskell 如何在我的LISP中实现宏系统,haskell,macros,lisp,implementation,Haskell,Macros,Lisp,Implementation,我正在写我自己的LISP,基于48小时内给自己写一个方案。(代码是。)作为最后一个练习,我想实现宏。考虑到我将表达式表示为不可变数据类型的列表,如何实现这一点。这可以简单地在LISP中完成,还是必须在Haskell中实现一些函数 我当前的实现是用Haskell编写的,工作原理大致如下: data Expr = Sym String | List [Expr] | Num Int | Str String | Bool Bool | Func Env [String] Ex

我正在写我自己的LISP,基于48小时内给自己写一个方案。(代码是。)作为最后一个练习,我想实现宏。考虑到我将表达式表示为不可变数据类型的列表,如何实现这一点。这可以简单地在LISP中完成,还是必须在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)
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.
  • 解析输入并将其转换为表达式列表
  • 计算表达式并将其替换为单个表达式
  • 返回该表达式并打印它
表达式在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)
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.
好了,现在谈谈真正的问题。宏不会计算其参数,而是通过将参数“放置”在表单中,将其转换为表达式。返回有效的表达式,该表达式可以作为带引号的列表进行计算或返回。我想通过一个特殊的评估函数来实现这个功能,它只评估宏形式中的符号。然而,如何做到这一点是我难以理解的。正确的解决方案是,我应该通过用参数替换表单中的符号来“简单地”修改表单,但由于Haskell的不变性,这是不可能的

所以,似乎已经用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 env(List(op:args))=do
函数IOERROR Expr
apply(Prim func)args=liftToIO$func args
应用(操作函数)参数=函数参数
应用(函数环境参数表单)参数=
大小写长度参数==的长度参数
True->(liftIO$bind env$zip参数参数)
>>=翻转评估表
假->投掷者。努马格斯。toInteger$length参数
应用u u=错误“应用”
因此,如果我想实现一个宏系统,那么我可能会删除参数的求值部分,然后将宏参数绑定到它的参数,并有一个特殊的求值,它只对表单中的每个符号求值,返回一个新表单,该表单中放有参数。这是我无法实现的,我甚至不确定逻辑是否正确


我知道这个问题相当广泛,可以更简单地用英语提问“如何在用Haskell编写的LISP实现中实现宏系统

您不需要特殊版本的
apply
。只需调用常规的
apply
,而不计算参数,然后
eval
apply

返回的表达式。不,宏不能在Lisp本身中实现,这就是它们的全部要点。作为加载/编译/处理给定表达式的一部分,您必须根据其定义对每个宏调用进行宏扩展

您必须更改
eval
实现,以便对未计算的参数调用宏,并将结果反馈到
eval
(而不是
apply
,就像处理普通函数应用程序一样)。正如注释中的
sepp2k
所建议的,您可以将宏表示为
Func…
表达式,但将它们保存在单独的环境中,其中只存储宏


另请参阅:

您可以尝试阅读计算机程序结构和实现中的解释器实现。您清楚显示的
eval
函数仅适用于默认的求值规则,而不适用于本书所称的特殊表单

正常的Lisp
eval
函数看起来更像这样:

data Expr
  = Sym String
  | List [Expr]
  | Num Int
  | Str String
  | Bool Bool
  | Func Env [String] Expr
  | Prim ([Expr] -> ErrorOr Expr)
  | Action ([Expr] -> IOErrorOr Expr)
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.
eval env expr@(列表)
|isSpecialForm env expr=评估特殊环境expr
|否则=evalApplication env expr
evalApplication env(op:args)=do
功能。。。
--等等。

您可以对参数/args长度不匹配(比出错有趣得多)实现自动咖喱或varargs(处理带有点的arglist)。:)宏如何成为有效的表达式类型?您是否在考虑类似于
宏lambda
?这不是Lisps处理宏的方式(即宏不能传递或存储在变量中)。还是说宏调用是一个表达式?这是真的,但是在他的
Expr
类型中,宏调用已经表示为
List
s,其第一个元素是包含宏名称的
Sym
。@sepp2k的Lisp不需要识别
(defmacro…
表单,才能有宏吗?现在它肯定能识别
(lambda…
,并将它们翻译成
Func…
表达式。他将把
(defmacro…
表格翻译成哪个
Expr
?我认为,另一种方法当然是将整个宏处理推进到实现中。读取
defmacro
表单将改变一些运行时内部,调用
macroexpand-1
将访问这些内部,等等。是的,这也是可能的。不过,它更不适合“在Lisp自身中”实现。应该可以将
defmacro
作为
操作实现。我想这就是
定义
的方式,例如,`已经实现了。
lambda
不能作为
操作实现的原因是它需要关闭环境,但是
defmacro
没有同样的问题。实际上我只是查看了github存储库,OP通过简单地处理
List[Sym“def”,…]来实现
def
defn
直接在
eval
功能中。所以我想他会用同样的方法