为clojure编写嵌套定义语句(如在方案中)的标准方法是啥?
Posted
技术标签:
【中文标题】为clojure编写嵌套定义语句(如在方案中)的标准方法是啥?【英文标题】:What is the standard way to write nested define statements (like in scheme) for clojure?为clojure编写嵌套定义语句(如在方案中)的标准方法是什么? 【发布时间】:2012-04-11 01:32:17 【问题描述】:所有示例均取自 SICP Book:http://sicpinclojure.com/?q=sicp/1-3-3-procedures-general-methods
这源于 MIT 的 LISP 视频系列 - http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-001-structure-and-interpretation-of-computer-programs-spring-2005/video-lectures/2a-higher-order-procedures/
在scheme中,你可以把'define'放在另一个'define'里面:
(define (close-enough? v1 v2)
(define tolerance 0.00001)
(< (abs (- v1 v2)) tolerance ) )
在 clojure 中,有一个 'let' 语句,唯一的区别是它是嵌套的:
(defn close-enough? [v1 v2]
(let [tolerance 0.00001]
(< (Math/abs (- v1 v2) )
tolerance) ) )
但是用 clojure 重写像这样更大的东西呢?:
(define (sqrt x)
(define (fixed-point f first-guess)
(define (close-enough? v1 v2)
(define tolerance 0.00001)
(< (abs (- v1 v2)) tolerance))
(define (try guess)
(let ((next (f guess)))
(if (close-enough? guess next)
next
(try next))))
(try first-guess))
(fixed-point (lambda (y) (average y (/ x y)))
1.0))
这确实有效,但看起来非常不合常规......
(defn sqrt [n]
(let [precision 10e-6
abs #(if (< % 0) (- %) %)
close-enough? #(-> (- %1 %2) abs (< precision))
averaged-func #(/ (+ (/ n %) %) 2)
fixed-point (fn [f start]
(loop [old start
new (f start)]
(if (close-enough? old new)
new
(recur new (f new) ) ) ) )]
(fixed-point averaged-func 1) ) )
(sqrt 10)
2012 年 3 月 8 日更新
感谢您的回答!
本质上,'letfn' 与 'let' 并没有太大的不同——被调用的函数必须嵌套在 'letfn' 定义中(与 Scheme 不同,Scheme 中的函数在其定义之后的下一个 sexp 中使用并且仅存在在定义它的***函数的范围内)。
所以另一个问题......为什么clojure不提供执行方案的能力?这是某种语言设计决定吗?我喜欢计划组织的地方是:
1) 想法的封装,这样我作为程序员就知道哪些小块正在被使用更大的块 - 特别是如果我只在大块中使用小块一次(无论出于何种原因,即使小块本身很有用)。
2) 这也停止了使用对最终用户无用的小程序来污染命名空间(我编写了 clojure 程序,一周后回到他们那里,不得不重新学习我的代码,因为它是一个扁平的结构,我觉得我是从里到外查看代码,而不是以自上而下的方式)。
3) 一个通用的方法定义接口,这样我就可以拉出一个特定的子方法,取消缩进对其进行测试,然后将更改后的版本粘贴回去,而无需过多摆弄。
为什么这不是在 clojure 中实现的?
【问题讨论】:
这是一个有趣的问题,但我不认为我会尝试将所有这些都放在一个函数中。 【参考方案1】:在 clojure 中编写嵌套命名过程的标准方法是使用 letfn
。
顺便说一句,您使用嵌套函数的示例非常可疑。示例中的所有函数都可以是***的非本地函数,因为它们本身或多或少有用,并且除了彼此之外不会关闭任何东西。
【讨论】:
谢谢你!请查看更新后的问题!【参考方案2】:批评他在所有一个功能中放置此功能的人,不理解为什么以这种方式完成它的上下文。 SICP 是示例的来源,它试图说明模块的概念,但没有向基础语言添加任何其他结构。所以“sqrt”是一个模块,它的接口中有一个函数,其余的是该模块中的本地或私有函数。我相信这是基于 R5RS 方案,后来的方案添加了我认为的标准模块构造(?)。但不管怎样,它更多地展示了隐藏实现的原理。
经验丰富的计划者也会经历类似的嵌套局部函数示例,但通常既隐藏实现又关闭值。
但是,即使这不是一个教学示例,您也可以看到这是一个非常轻量级的模块,我可能会在更大的“真实”模块中以这种方式编写它。重复使用是可以的,如果它有计划的话。否则,您只是暴露了可能不完全适合您以后需要的功能,同时给这些功能增加了可能会在以后破坏它们的意外用例。
【讨论】:
【参考方案3】:letfn
是标准方式。
但由于 Clojure 是一个 Lisp,您可以创建(几乎)任何您想要的语义。这是一个概念证明,它根据letfn
定义define
。
(defmacro define [& form]
(letfn [(define? [exp]
(and (list? exp) (= (first exp) 'define)))
(transform-define [[_ name args & exps]]
`(~name ~args
(letfn [~@(map transform-define (filter define? exps))]
~@(filter #(not (define? %)) exps))))]
`(defn ~@(transform-define `(define ~@form)))))
(define sqrt [x]
(define average [a b] (/ (+ a b) 2))
(define fixed-point [f first-guess]
(define close-enough? [v1 v2]
(let [tolerance 0.00001]
(< (Math/abs (- v1 v2)) tolerance)))
(define tryy [guess]
(let [next (f guess)]
(if (close-enough? guess next)
next
(tryy next))))
(tryy first-guess))
(fixed-point (fn [y] (average y (/ x y)))
1.0))
(sqrt 10) ; => 3.162277660168379
对于实际代码,您可能希望将 define
更改为更像 R5RS:允许非 fn 值,在 defn
、defmacro
、let
、letfn
和 @987654330 中可用@,并验证内部定义是否位于封闭体的开头。
注意:我必须将 try
重命名为 tryy
。显然try
是一个特殊的非函数、非宏结构,其重新定义静默失败。
【讨论】:
这很酷。我希望我能给你的答案更多的支持!以上是关于为clojure编写嵌套定义语句(如在方案中)的标准方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章