【问题标题】: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 中实现的?

【问题讨论】:

  • 这是一个有趣的问题,但我不认为我会尝试将所有这些都放在一个函数中。

标签: clojure scheme idioms


【解决方案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 值,在 defndefmacroletletfn 和 @987654330 中可用@,并验证内部定义是否位于封闭体的开头。

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

    【讨论】:

    • 这很酷。我希望我能给你的答案更多的支持!
    猜你喜欢
    • 2019-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-30
    • 1970-01-01
    • 2013-07-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多