所有示例均来自SICP Book:http://sicpinclojure.com/?q=sicp/1-3-3-procedures-general-methods
这是出自麻省理工学院关于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/
在方案中,你可以将'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) ) )
但是如果在这样更大的东西中重写一下呢?
(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相反,其中函数在定义后的下一个sexp中使用,并且仅存在在其定义的顶级函数的范围内)。
另一个问题......为什么clojure不能提供做出什么方案的能力?这是某种语言设计决定吗?我对计划组织的看法是:
1)封装思想,以便我作为程序员知道哪些小块被利用更大块 - 特别是如果我只在大块中使用一次小块(无论出于何种原因) ,即使这些小块本身也很有用)。
2)这也会停止使用对最终用户无用的小程序来污染命名空间(我写了一些clojure程序,一周后又回来了,不得不重新学习我的代码,因为它是一个扁平的结构,我觉得我在内部看代码而不是自上而下的方式。
3)一个常见的方法定义界面,所以我可以拉出一个特定的子方法,对它进行去缩进测试,然后将更改后的版本粘贴回去而不需要太多的摆弄。
为什么不在clojure中实现?
答案 0 :(得分:16)
在clojure中编写嵌套的命名过程的标准方法是使用letfn
。
另外,您使用嵌套函数的示例非常可疑。示例中的所有函数都可以是顶级非本地函数,因为它们本身或多或少都有用,并且不会关闭除了彼此之外的所有函数。
答案 1 :(得分:13)
人们批评他将这一点放在所有一个功能中,并不理解为什么以这种方式完成它的背景。 SICP就是这个例子的来源,它试图说明模块的概念,但没有在基础语言中添加任何其他结构。所以“sqrt”是一个模块,在其接口中有一个功能,其余的是该模块中的本地或私有功能。这是基于我认为的R5RS方案,后来的方案已经添加了我认为的标准模块构造(?)。但无论如何,它更多地展示了隐藏实现的原则。
经验丰富的策划者也经历了嵌套本地函数的类似示例,但通常同时隐藏实现和关闭值。
但即使这不是一个教学示例,你也可以看到这是一个非常轻量级的模块,我可能会在更大的“真实”模块中以这种方式编写它。重新使用是好的,如果它的计划。否则,您只是暴露可能不适合以后需要的功能,同时,将这些功能加重到可能在以后破坏它们的意外用例。
答案 2 :(得分:6)
letfn
是标准方式。
但是由于Clojure是一个Lisp,你可以创建(几乎)你想要的任何语义。以下是根据define
定义letfn
的概念验证。
(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
和fn
,并验证内部定义是否位于封闭体的开头。
注意:我必须将try
重命名为tryy
。显然try
是一个特殊的非函数非宏构造,重新定义会以静默方式失败。