Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/clojure/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Clojure 宏观成分_Clojure_Macros - Fatal编程技术网

Clojure 宏观成分

Clojure 宏观成分,clojure,macros,Clojure,Macros,我正在玩Clojure宏,特别是尝试map的宏版本,我很有想象力地将其命名为“mmap”: 请不要深入探究我这样做的原因。这只是一个最小的可重复的例子 只要将集合传递给它,它就可以正常工作,但当我尝试用另一个宏组合它时,mmap会用宏本身而不是它的扩展来分割sexp 我希望“mmap”的工作方式如下: (mmap inc [1 2 3 4]) => [2 3 4 5] (mmap dec (mmap inc [1 2 3 4])) => [1 2 3 4] NO

我正在玩Clojure宏,特别是尝试map的宏版本,我很有想象力地将其命名为“mmap”:

请不要深入探究我这样做的原因。这只是一个最小的可重复的例子

只要将集合传递给它,它就可以正常工作,但当我尝试用另一个宏组合它时,mmap会用宏本身而不是它的扩展来分割sexp

我希望“mmap”的工作方式如下:

(mmap inc [1 2 3 4])            => [2 3 4 5]
(mmap dec (mmap inc [1 2 3 4])) => [1 2 3 4] NOPE. See the edit.
所以问题是:有没有办法强制将一个宏作为参数传递给另一个宏?在这种情况下,如何

感谢您的任何见解

编辑我刚刚注意到我的示例没有意义,传递mmap宏的结果就像调用:

(mmap inc (vector 1 2 3 4))
这在这个特殊的案例中毫无意义,但我仍然想知道它是否能以某种方式实现


EDIT2使用[1 2 3 4]数组而不是“arg”这样的符号是为了传达整个过程是在编译时完成的,而不是在运行时。

函数总是在调用函数之前对其参数进行求值,导致表达式以熟悉的“由内而外”模式求值:

的目的是避免在调用函数之前对参数进行求值,从而导致表达式的求值结果为“由外而内”。这样,就可以像编写新的语言功能一样编写宏:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test))

(defmacro infix
  [[l op r]]
  `(~op ~l ~r))

(defmacro snoop
  [& forms]
  `(let [result# ~@forms]
     (do
       (println "snoop => " result#)
       result#)))

(dotest
  (spyx (infix (2 + 3)))
  (spyx (infix (5 - 3)))
  (newline)

  (snoop (+ 1 2))
  (snoop (infix (4 + 5)))
  (newline)

  (println :last
    (infix ((snoop 4) + (snoop 5)))))
结果

(infix (2 + 3)) => 5
(infix (5 - 3)) => 2

snoop =>  3
snoop =>  9

snoop =>  4
snoop =>  5
:last 9
最后一个例子有一个中间阶段,看起来像:

(println :last
  (+ (snoop 4) (snoop 5)))
在哪里可以看到
infix
在2
snoop
调用之前已经被计算过。这正是宏的强大之处,如果没有它,宏将毫无用处

您可以创建一个新宏,其工作方式如下:

(defmacro mmap
  [fns coll]
  `(let [comp-fn# (comp ~@fns)]
     (mapv comp-fn# ~coll)))

(mmap [inc inc inc] [1 2 3]) => [4 5 6]

虽然上面的
mmap
有效,但它实际上是对宏系统的滥用,如果您给它一些编译时常量以外的东西,它可能会失败

基本函数的一个更好的替代方法是对正常函数的简单记忆:

(def do-stuff
  (memoize ; will only execute on the first usage
    (fn
      [coll]
      (mapv #(* 2 %) (mapv inc coll)))))

(do-stuff [1 2 3 4]) => [4 6 8 10]
如果您认为即使在运行时进行一次调用也太多,我建议您在预编译步骤中手动运行“类似宏”的代码,并将结果保存在新的“源文件”中,然后可以像往常一样进行编译(例如,您可能正在预计算一组素数)

结果是宏系统仅用于操作代码以添加新的语言功能。使用不同的技术可以更好地实现任何其他目标


关于
mapv

我也在几个月后偶然发现了
mapv

请确保为其添加书签并始终打开浏览器选项卡。定期学习,直到你能记住每个功能。:)

还要注意的是,这里没有列出一些更模糊的项目


更新:


宏的“杀手特性”是添加新的语言特性。在Java等语言中,只能添加新的库。例如,上面的spyx是一个与snoop示例非常相似的宏。有关其他示例,请参见例外默认值、VAL->map和it->from。事实上,许多“核心”Clojure功能(如for表达式或and&or逻辑运算符)实际上是任何应用程序都可以编写(或修改!)的宏。

宏扩展从最外层的形式开始工作,并重复调用,直到达到固定点为止。这意味着从宏中,您将获得未展开的代码。如果您希望它像函数一样工作,即在调用函数之前对参数求值,那么这可能是不合理的。使用宏时,不会展开任何内容,也不会计算任何内容,这使得您可以按照自己的意愿解释底层代码

但您也可以从宏中自由调用自己,这将确保首先展开嵌套表单。在您的例子中,这意味着使用文本值时,您将返回数据,并且您可以处理macroexpanded值,就像它被直接提供给您的宏一样


但请注意,只有当对宏的最内部调用被赋予一个文本值并返回一个文本值时,这才起作用。在任何情况下,宏都无法知道运行时哪个值可用。通常人们会尝试直接在表单上调用
eval
,但这不是一个好主意,因为代码依赖于运行时上下文,一般来说,在宏扩展/编译过程中对应该在运行时进行评估的代码进行评估是没有意义的。

您可以在
coll
上调用macroexpand,以确保首先进行宏扩展,前提是您始终有文字参数。如果集合是仅在运行时才知道的值,则在宏扩展期间无法对其应用函数。是的,整个过程是在宏扩展期间执行的,而不是在运行时执行的。还有,@coredump谢谢你的提示!既然你的建议行得通,你介意把它总结成一个答案,这样我就可以接受了吗?一个非常详尽和有教育意义的答案!我的实际代码是为了做一些不同于(令人困惑的)示例的事情。我想组合一个“映射”宏和一个“过滤”宏,但读了你的解释后,我意识到a)即使我让它工作起来,它确实是一件脆弱的事情b)我的思维方式是错误的:被评估的宏不是我们想象的那样,最终复合宏将是一个更好的解决方案。虽然我对编译时/vs运行时有相当的理解,但您的回答帮助我进一步了解了这一点,这也是我接受它的原因。非常感谢。顺便说一句,我不知道有mapv。在写了这么多次之后,我应该知道:p不确定您打算用“映射宏”和“过滤宏”编写什么。如果
(map fa(filter fb coll))
或类似
(>>coll(filter fb)(map fa))
的线程的基本组成不可取,请记住也检查Clojure传感器。我的目标是消除过度使用
(defmacro mmap
  [fns coll]
  `(let [comp-fn# (comp ~@fns)]
     (mapv comp-fn# ~coll)))

(mmap [inc inc inc] [1 2 3]) => [4 5 6]
(def do-stuff
  (memoize ; will only execute on the first usage
    (fn
      [coll]
      (mapv #(* 2 %) (mapv inc coll)))))

(do-stuff [1 2 3 4]) => [4 6 8 10]