当前位置: 首页 > news >正文

【Janet】宏

Janet支持宏:接受代码做为输入并输出转换后的代码的程序。宏类似于函数,但是它转换的是代码本身,而不是数据,因此比函数更灵活。宏可以让你扩展语言本身的语法。

我们已经见过了一些宏。 letloopdefn 表达式都是宏。当编译器遇到宏时,首先求值宏,然后编译求值结果。当编译器对宏求值之后我们就说这个宏已经展开了。简单来说, defn 宏可以被认为是将下面的形式

(defn1 myfun [x] body)

转换成

(def myfun (fn myfun [x] body))

我们可以这样编写这个宏:

(defmacro defn1 [name args body](tuple 'def name (tuple 'fn name args body)))

虽然这个宏还有些问题,但是对于简单的函数是完全可用的。

第一个问题是我们的 defn1 宏不能用来定义函数体中包含多个表达式的函数。我们可以使用可变参数,就像函数一样,下面是第二个版本。

(defmacro defn2 [name args & body](tuple 'def name (apply tuple 'fn name args body)))

很好,现在我们可以定义函数体包含多个元素的函数了。

我们还可以继续优化这个宏。首先我们可以给它添加一个文档。如果稍后有人使用这个宏,就可以使用 (doc defn3) 来获取文档。其次我们可以使用内置的 quasiquoting 来重新这个宏。

(defmacro defn3"Defines a new function."[name args & body]~(def ,name (fn ,name ,args ,;body)))

它的功能和前一个版本的 defn2 是相同的,这样写宏的输出更清晰。开头的 ~(quasiquote x) 表达式的语法糖,它类似于 (quote x) 除了在它内部可以 unquote 表达式。 nameargs 前面的逗号是 unquote,让我们可以将值放进 quasiquote。没有 unquote,符号 name 会被放进返回的元组中,然后我们定义的每一个函数都会叫做 name

表达式(代码)会被求值到一个值,quote 是将表达式保持为代码,而 unquote 是将代码再次求值为结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

除了 name,我们还必须 unquote body。但是不能使用普通的 unquote,我们可以看看直接使用 unquote 会发生什么。

(def name 'myfunction)
(def args '[x y z])
(defn body '[(print x) (print y) (print z)])~(def ,name (fn ,name ,args ,body))
# -> (def myfunction (fn myfunction (x y z) ((print x) (print y) (print z))))

函数体两边多出了一对额外的括号!我们并不想多余的括号,幸运的是,Janet 有 (splice x) 表达式可以实现这一点,使用他的简写形式 ;,结合 unquote 表达式,我们可以得到想到的输出:

~(def ,name (fn ,name ,args ,;body))
# -> (def myfunction (fn myfunction (x y z) (print x) (print y) (print z)))

意外的绑定捕获

当我们写宏是,有时必须生成一些本地绑定。以下面的宏为例:

(defmacro max1"Get the max of two values."[x y]~(if (> ,x ,y) ,x ,y))

它会求值 xy 两次,因为它们都在宏中出现了两次。例如, (max1 (do (print 1) 1) (do (print 2) 2)) 会打印 1 和 2 两次,这与我们预想的不同。

我们可以优化如下:

(defmacro max2"Get the max of two values."[x y]~(let [x ,xy ,y](if (> x y) x y)))

现在我们没有了重复求值问题,但同时也引入了一个更微妙的问题。猜猜看下面的代码会返回什么?

(def x 10)
(max2 8 (+ x 4))

我们希望最大值是14,但实际上它的结果是12。如果我们把宏展开就很好理解了。在 Janet 中我们可以使用 (macex1 x) 函数将宏展开一次。完全展开使用 (macex x),注意,Janet 有很多宏,完全展开可能让代码很难看懂。

(macex1 '(max2 8 (+ x 4)))
# -> (let (x 8 y (+ x 4)) (if (> x y) x y))

展开之后可以看到, y 错误的引用了宏内部的 x (它被绑定到8)而不是那个绑定到10的 x。问题是我们在宏内部重用了符号 x,它隐藏了之前的绑定。这个问题被叫做 hygiene 问题,许多语言(如 Scheme)通过 hygienic 宏来解决这个问题。Hygienic 宏可以防止这种意外捕获,但是也会对宏的编写产生一些限制。Janet 宏系统没有使用 hygienic 宏,参见 Kohlbecker et al 1986 的论文,而是需要程序员自己去避免意外捕获。

Janet 为这个问题提供了一个通用的解决方案,使用 (gensym) 函数,它会保证返回一个唯一且不与已定义的符号冲突的符号。下面是完全正确的版本。

(defmacro max3"Get the max of two values."[x y](def $x (gensym))(def $y (gensym))~(let [,$x ,x,$y ,y](if (> ,$x ,$y) ,$x ,$y)))

因为在宏里面创建多个唯一符号非常常见,Janet 提供了 with-syms 宏来简化代码。

(defmacro max4"Get the max of two values."[x y](with-syms [$x $y]~(let [,$x ,x,$y ,y](if (> ,$x ,$y) ,$x ,$y))))

如你所见,宏威力巨大但也容易出bug。你需要牢记,宏只是输出代码的函数,而且它输出的代码必须在任何场景都能工作。多数情况下使用函数就足够了,而且比宏更有用,因为函数做为一等公民可以被任意传递。

http://www.dtcms.com/a/611777.html

相关文章:

  • 石头剪刀布小游戏开发
  • 【算法】线性回归
  • 中英文外贸网站源码网页图片提取器
  • 怎么样免费做网站宁夏枸杞网站建设方案
  • wap网站模板下载淮北网站开发公司
  • 民权平台网站建设网页打不开connection
  • 关于加强网站建设的建议企业进行网站建设的方式有( )
  • 教育网站开发需求分析创网易邮箱账号
  • 梅河口市建设局网站建设银行忘记密码网站首页
  • 网站建设有哪些内容苏州建筑设计公司
  • 查询邮箱注册过的网站网站建设甲方给乙方的需求方案
  • 怎么搭建自己的网站卖货网站推广seo
  • 如何写网站建设方案书浙江公铁建设工程有限公司网站
  • 深圳做二维码网站网站建设费用怎么做分录
  • 韩都衣舍网站建设的改进入驻天猫店需要什么条件
  • windows下特定字符搜索功能
  • 网站后台模板安装显示不了服务器价格购买价格表
  • 2025-11-13~14 hetao1733837的刷题记录
  • 建设一个门户网站需要多少钱网站留言效果怎么做
  • 常见问题 网站建设家具网站建设的背景
  • 网站建设从入门到精通pdf毕业答辩问题怎么做的这个网站
  • 爱网站在线观看免费网站建设更新不及时
  • 同城网站开发公司做网站服务费怎样做账
  • 4399页游网站建立公司微信平台 网站平台
  • 使用streamlit和qwen-agent实现Qwen3-VL模型图文对话应用的思路
  • ModelScope-Agent框架:前世今生与技术详解
  • EG3033 三相P/N MOS管栅极驱动芯片技术解析
  • XPath 实例
  • 网站建设 制作教程网站建设中 动态图片
  • 企业门户网站费用实验中心网站建设