为clojure编写嵌套定义语句(如方案中)的标准方法是什么?

时间:2012-03-27 13:50:09

标签: clojure scheme idioms

所有示例均来自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中实现?

3 个答案:

答案 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值,可在defndefmacrolet中使用,letfnfn,并验证内部定义是否位于封闭体的开头。

注意:我必须将try重命名为tryy。显然try是一个特殊的非函数非宏构造,重新定义会以静默方式失败。