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

zca*_*ate 11 scheme idioms clojure

所有示例均来自SICP Book:http://sicpinclojure.com/?q = sysp/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 /视频讲座/ 2A-高次步骤/

在方案中,您可以将'define'放在另一个'define'中:

(define (close-enough? v1 v2)
    (define tolerance 0.00001)
    (< (abs (- v1 v2)) tolerance ) )
Run Code Online (Sandbox Code Playgroud)

在clojure中,有'let'语句,唯一的区别是它是嵌套的:

(defn close-enough? [v1 v2]
    (let [tolerance 0.00001]
         (< (Math/abs (- v1 v2) ) 
            tolerance) ) )
Run Code Online (Sandbox Code Playgroud)

但是如果在这个更大的东西里重写一些更大的东西呢?:

(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))
Run Code Online (Sandbox Code Playgroud)

这确实有效,但看起来非常传统......

(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)
Run Code Online (Sandbox Code Playgroud)

2012年3月8日更新

谢谢你的回答!

基本上'letfn'与'let'没有太大区别 - 被调用的函数必须嵌套在'letfn'定义中(而不是Scheme,其函数在定义后的下一个sexp中使用,并且只存在于范围内)定义它的顶级函数).

所以另一个问题是......为什么clojure不能提供做出什么方案的能力?这是某种语言设计决定吗?我喜欢的计划组织是:

  • 1)思想的封装让我作为程序员知道哪些小块被利用更大的块 - 特别是如果我只在大块中使用一次小块(无论出于何种原因,即使是小块本身就很有用).

  • 2)这也停止了用最终用户无用的小程序来污染命名空间(我写了一些clojure程序,一周后回到他们身边,不得不重新学习我的代码,因为它是一个扁平结构,我觉得我在看里面的代码而不是自上而下的方式.

  • 3)一个常见的方法定义接口,所以我可以拉出一个特定的子方法,对它进行去缩进测试,然后将更改后的版本粘贴回去而不需要太多的摆弄.

为什么不在clojure中实现?

Joo*_*aat 16

在clojure中编写嵌套的,命名的过程的标准方法是使用letfn.

另外,您使用嵌套函数的示例非常可疑.示例中的所有函数都可以是顶级非本地函数,因为它们本身或多或少都有用,并且除了彼此之外不会关闭任何东西.


小智 13

人们批评他将这一点放在所有一个功能中,并不理解为什么以这种方式完成它的背景.SICP就是这个例子的来源,它试图说明模块的概念,但没有在基础语言中添加任何其他结构.所以"sqrt"是一个模块,在其接口中有一个功能,其余的是该模块中的本地或私有功能.这是基于我认为的R5RS方案,后来的方案已经添加了我认为的标准模块构造(?).但无论如何,它更多地展示了隐藏实现的原则.

经验丰富的策划者也经历了嵌套本地函数的类似示例,但通常同时隐藏实现和关闭值.

但即使这不是一个教学示例,您也可以看到这是一个非常轻量级的模块,我可能会在更大的"真实"模块中以这种方式编写它.重新使用是好的,如果它的计划.否则,您只是暴露可能不适合以后需要的功能,同时,将这些功能加重到可能在以后破坏它们的意外用例.


Pet*_*ton 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
Run Code Online (Sandbox Code Playgroud)

对于真正的代码,你想改变define的行为更像R5RS:允许非FN值,在可用的defn,defmacro,let,letfn,和fn,并验证内部定义是在封闭体的开始.

注意:我必须重命名trytryy.显然try是一个特殊的非功能,非宏构造,重新定义无声地失败.