我喜欢我的代码具有“自上而下”的结构,这意味着我想做的事情与Clojure中的自然情况完全相反:在使用函数之前先定义函数。不过,这应该不成问题,因为从理论上讲,我可以先declare
进行所有功能,然后继续享受生活。但实际上,declare
似乎无法解决每个问题,我想了解以下代码无法正常工作的确切原因。
我有两个函数,我想通过将两者组合来定义第三个函数。以下三段代码完成了此任务:
1
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
(defn mycomp [x] (f (g x)))
(println (mycomp 10))
2
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
(def mycomp (comp f g))
3
(declare f g)
(defn mycomp [x] (f (g x)))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
但是我真的很想写
(declare f g)
(def mycomp (comp f g))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
那给了我
Exception in thread "main" java.lang.IllegalStateException: Attempting to call unbound fn: #'user/g,
这将意味着在许多情况下都可以进行前向声明,但是在某些情况下,我不能仅仅declare
我的所有函数并以我喜欢的任何方式和顺序来编写代码。此错误的原因是什么?前向声明实际上允许我做什么,以及在什么情况下我必须已经定义了函数,例如在这种情况下使用comp
?我怎么知道何时必须严格定义?
答案 0 :(得分:6)
如果您利用Clojure(记录不充分)的var
行为,就可以实现自己的目标:
(declare f g)
(def mycomp (comp #'f #'g))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
(mycomp 10) => 45
请注意,语法#'f
只是转换为(var f)
的简写(技术上是“阅读器宏”)。因此,您可以直接编写以下代码:
(def mycomp (comp (var f) (var g)))
并获得相同的结果。
请see this answer详细了解Clojure符号(例如f
)与该符号所指向的(匿名)Clojure var之间的相互作用(大部分是隐藏的),即{ {1}}或#'f
。然后,该var然后指向一个值(例如您的函数(var f)
。
在编写(fn [x] (* x 3))
之类的表达式时,会出现两步间接访问。首先,对符号(f 10)
进行“求值”以找到相关的变量,然后对变量{进行求值”以查找相关的函数。大多数Clojure用户并没有真正意识到此两步过程的存在,并且几乎所有时间我们都可以假装符号f
与函数值f
之间存在直接联系。>
原始代码无法正常工作的具体原因是
(fn [x] (* x 3))
创建2个“空”变量。就像(declare f g)
在符号(def x)
和一个空var之间创建关联一样,x
也是如此。因此,当declare
函数尝试从comp
和f
提取 值 时,则不存在任何内容:变量存在但它们是空的。
P.S。
以上是一个例外。如果您使用g
表格或类似表格,则不涉及let
:
var
在(let [x 5
y (* 2 x) ]
y)
;=> 10
格式中,不存在var。相反,编译器在符号及其关联值之间建立直接连接。即let
和x => 5
。
答案 1 :(得分:3)
我认为Alan的回答很好地解决了您的问题。您的第三个示例之所以有效,是因为您没有将函数作为参数传递给mycomp
。我会重新考虑尝试以“反向”顺序定义事物,因为它与基本语言设计背道而驰,需要更多代码,并且可能使其他人难以理解。
但是...只是为了开怀大笑,并演示Clojure宏的可能,这是comp
的另一种(更差的)实现,可用于您的首选语法,而无需直接处理vars:
(defn- comp-fn-arity [variadic? args f & fs] ;; emits a ([x] (f (g x)) like form
(let [args-vec (if variadic?
(into (vec (butlast args)) ['& (last args)])
(apply vector args))
body (reduce #(list %2 %1)
(if variadic?
(apply list 'apply (last fs) args)
(apply list (last fs) args))
(reverse (cons f (butlast fs))))]
`(~args-vec ~body)))
(defmacro momp
([] identity)
([f] f)
([f & fs]
(let [num-arities 5
args-syms (repeatedly num-arities gensym)]
`(fn ~@(map #(apply comp-fn-arity (= % (dec num-arities)) (take % args-syms) f fs)
(range num-arities))))))
这将发出类似于comp
的实现:
(macroexpand '(momp f g))
=>
(fn*
([] (f (g)))
([G__1713] (f (g G__1713)))
([G__1713 G__1714] (f (g G__1713 G__1714)))
([G__1713 G__1714 G__1715] (f (g G__1713 G__1714 G__1715)))
([G__1713 G__1714 G__1715 & G__1716] (f (apply g G__1713 G__1714 G__1715 G__1716))))
之所以可行,是因为您的(未绑定)函数没有作为值传递给另一个函数;在编译过程中,宏将“就地”展开,就像您是手工编写合成函数一样,如第三个示例中所示。
(declare f g)
(def mycomp (momp f g))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
(mycomp 10) ;; => 45
(apply (momp vec reverse list) (range 10)) ;; => [9 8 7 6 5 4 3 2 1 0]
在某些其他情况下(例如((momp - dec) 1)
之所以失败,是因为dec
被内联了 ,并且没有0-arg arity来匹配宏的0-arg arity。再说一次,这只是为了举例,我不建议这样做。