在Clojure中执行动态绑定的函数

5
我希望能够在数据结构中预存一些函数调用,并在另一个函数内部稍后评估/执行它们。对于使用defn定义的命名空间级别的函数,这将按计划工作(即使函数定义在我创建数据结构之后),但对于在函数内部使用let [name (fnletfn定义的函数则无法正常工作。
下面是一个简单的自包含示例:
(def todoA '(funcA))
(def todoB '(funcB))
(def todoC '(funcC))
(def todoD '(funcD)) ; unused

(defn funcA [] (println "hello funcA!"))

(declare funcB funcC)

(defn runit []
    (let [funcB (fn [] (println "hello funcB"))]
    (letfn [(funcC [] (println "hello funcC!"))]
        (funcA)       ; OK
        (eval todoA)  ; OK
        (funcB)       ; OK
        (eval todoB)  ; "Unable to resolve symbol: funcB in this context" at line 2
        (funcC)       ; OK
        (eval todoC)  ; "Unable to resolve symbol: funcC in this context" at line 3
)))

如果你好奇我的测试设置,想要查看这6个语句的结果,我会注释/取消注释一些OK /失败的行,然后从REPL中调用(runit)。
有没有简单的解决方案可以让在另一个函数内定义的函数的eval'd quote'd调用函数正常工作?
更新:
这种方法(基于danlei的建议)确实有效。让我们看看我能否在“现实生活”中让这种方法起作用!
(def todoB '(funcB))
(declare funcB)

(defn runit []
  (binding [funcB (fn [] (println "hello funcB"))]
    (funcB)
    (eval todoB)  ; "Unable to resolve symbol: funcB in this context" at line 1!
))

更新:

这段代码将会被用于我的解决方案,解决一个约束满足问题 - 我想要找出谁是斑马的主人! 我对Clojure和函数式编程都比较陌生,这让我练习变得更加有挑战性。我会遇到许多困难,但这是学习过程中的一部分。

我曾经将约束条件指定为一堆简单的向量,就像这样:

[:con-eq :spain :dog]
[:abs-pos :norway 1]
[:con-eq :kools :yellow]
[:next-to :chesterfields :fox]

每个向量的第一个元素指定了约束的类型。但这会导致一种尴尬的实现方式,需要为这些规则编写调度机制,因此我决定将它们编码为(引用)函数调用:

'(coloc :japan :parliament) ; 10
'(coloc :coffee :green) ; 12
'(next-to :chesterfield :fox) ; 5

所以我可以通过简单的eval调度约束规则。这似乎更加优雅和“lisp-y”。然而,每个函数都需要访问我的域数据(命名为vars),并且该数据在程序运行时不断变化。我不想通过引入额外的参数来玷污我的规则,因此我希望通过动态作用域使varseval的函数可用。
我现在已经学会了使用binding进行动态作用域,但它也需要一个declare

1
你是在试水还是真的要实现一些东西?如果是后者,我想知道你正在尝试做什么,以至于被迫使用这样的设计 - 乍一看,延迟或普通的闭包可能就能解决问题了。 - cgrand
@cgrand:感谢您的关注!我已经添加了第二个更新来解释我正在尝试做什么。目前这对我来说是有效的,但我当然也乐意听取更好的建议! - Carl Smotricz
@cgrand:我的帖子约束满足问题现在已经有了我完整的解决方案。如果你感兴趣,你可以在那里看到binding的实际应用。我非常乐意听取对我的业余方法的建设性批评。 - Carl Smotricz
2个回答

5
你的意思是像这样吗?
(def foo '(bar))
(declare bar)

(binding [bar (fn [] (println "hello bar"))]
  (eval foo))

如果是的话,你的问题就简化为这个:
(let [foo 1]
  (eval 'foo))

这样做行不通,因为eval不会在词法环境中进行评估。您可以使用vars来解决这个问题:

(declare foo)

(binding [foo 1]
  (eval 'foo))

就这个问题而言,Clojure与CL似乎具有类似的语义,参见CLHS

在当前动态环境和空词法环境中评估表单。


是的,看起来可以工作。很高兴在SO上有越来越多精通Clojure的人。谢谢! - Carl Smotricz
不用谢。我想这是为了教育目的。否则,请考虑cgrand对你的问题的评论。 - danlei

3
我认为您正在解决错误的问题。在函数式编程语言中,函数是值,可以分配给任何可以存储其他任何值的东西,例如映射。您不应该尝试操作命名空间或对任何内容进行评估 - 这不是perl。
请尝试类似以下的方法,并使用assoc在本地更改映射:
user=> (def fnmap {:funcA (fn [x] (inc x)), :funcB (fn [x] (* x 2))})
#'user/fnmap
user=> ((:funcA fnmap) 10)
11
user=> ((:funcB fnmap) 10)
20

谢谢!我很高兴确认这几乎就是我正在做的事情。我的工作解决方案的编码可以在我的约束满足问题帖子中找到。 - Carl Smotricz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接