【问题标题】:How to implement a macro system into my LISP如何在我的 LISP 中实现宏系统
【发布时间】:2013-05-06 02:53:03
【问题描述】:

我正在编写我自己的 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(which macroexpand call)做了什么,它是否从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 实现中实现宏系统

【问题讨论】:

  • 您可以在参数/参数长度不匹配的情况下实现自动柯里化或可变参数(处理带有 dot 的参数列表)(比仅仅出错更有趣)。 :)

标签: haskell macros lisp implementation


【解决方案1】:

您可以尝试阅读计算机程序的结构和实现中的解释器实现。您清楚地展示的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】:

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

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

    另见:Lazy Evaluation vs Macros

    【讨论】:

    • 宏是一种有效的表达式类型吗?你在想macro-lambda之类的东西吗?这不是 Lisps 处理宏的方式(即宏不是您可以传递或存储在变量中的东西)。或者你是说宏 calls 是一个表达式?确实如此,但在他的Expr 类型宏调用中已经表示为Lists,其第一个元素是包含宏名称的Sym
    • @sepp2k 他的 Lisp 不是必须识别 (defmacro ...) 表单才能拥有宏吗?现在它肯定能识别(lambda ...) 并将它们翻译成Func ... 表达式。他会将(defmacro ...) 翻译成哪个Expr?我收集到一些不同的东西。
    • @sepp2k 当然,另一种方式是将整个宏处理推入实现中。读取defmacro 表单会改变一些运行时内部结构,调用macroexpand-1 会访问这些内部结构,等等。是的,这也是可能的。尽管如此,它仍然更适合“在 Lisp 本身中”实现。
    • 应该可以将defmacro 实现为Action。我想这就是define,例如`已经实现的方式。 lambda不能实现为Action的原因是它需要关闭环境,但是defmacro没有同样的问题。
    • 其实我只是查看了github存储库,OP通过在eval函数中直接处理List [Sym "def", ...]来实现defdefn。所以我想他会对defmacro 使用相同的方法。
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 2011-03-28
      • 2013-11-09
      • 2017-07-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-03
      • 1970-01-01
      相关资源
      最近更新 更多