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

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

【讨论】:

这很酷。我希望我能给你的答案更多的支持!

以上是关于为clojure编写嵌套定义语句(如在方案中)的标准方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

Clojure学习笔记——函数式编程

在 Clojure 中将嵌套向量减少为另一个向量

如何在 Clojure 中递归展平任意嵌套的向量和映射?

在 Clojure 的嵌套映射中关联多个键/值的惯用方法是啥?

从嵌套地图(和矢量)创建 HTML 表格

Clojure:将嵌套矢量格式应用于展平矢量