【问题标题】:decent way of nested definition in scheme方案中嵌套定义的体面方式
【发布时间】:2014-01-03 04:18:03
【问题描述】:

我想使用一个辅助函数定义一个常量foo,比如bar。我想在foo的定义中隐藏bar,所以我来了这个代码:

(define foo
  (define (bar n)
    (+ n n))
  (bar 1))

但是,此定义会在许多方案实现(mit-scheme、racket、guile 等)中导致语法错误。

我有三种解决方法,但似乎都不令人满意:

(define foo1
  ((lambda ()
     (define (bar n)
       (+ n n))
     (bar 1))))

(define foo2
  (let ((bar (lambda (n) (+ n n))))
    (bar 1)))

(define (foo3)
  (define (bar n)
    (+ n n))
  (bar 1))

foo1 使用 lambda 来创建编写辅助定义的环境,而括号似乎有些混乱。

foo2 使用 let 表达式,但我不能再使用语法糖 (define (f n) ...) => (define f (lambda (n) ...))

foo3 与原来的相比,需要的修改更少,但每次我想要这个值时,我都必须调用(foo3) 并重新进行计算。

我的问题是:

  1. 我觉得这种嵌套定义是有道理的,但是为什么会被认为是语法错误呢?
  2. 有没有像样的方法来写foo的定义?

【问题讨论】:

  • foo1foo2 是等价的(let 在后台使用 lambda)。另一方面,foo3 定义了一个新函数,而不是像其他选项那样的常量值,所以它不等价。
  • 重写嵌套defines(递归的)的标准和正确方法是使用letrec:(define foo (letrec ((bar (lambda(n) (+ n n)))) (bar 1)))。这相当于(更流行的)“命名为let 构造(define foo (let bar ((n 1)) (+ n n)))(尽管bar 在这里根本没有被递归调用)。
  • (define foo1 ((lambda () (define (bar n) (+ n n)) (bar 1)))),这个有多余的括号,去掉lambda周围的一对括号;像这样 (define foo1 (lambda () (define (bar n) (+ n n)) (bar 1)))。它有效。

标签: scheme racket mit-scheme guile


【解决方案1】:

如果我正确理解您的问题,在 Racket 中执行此操作的另一种惯用方法是使用模块。

这个模块可以用一个单独的文件来定义:

;; foo.rkt
#lang racket
(define (bar n)
  (+ n n))
(define foo (bar 1))
(provide foo)

;; use-foo.rkt
#lang racket
(require "foo.rkt")
foo

或者通过一个文件中的module 表单:

#lang racket
(module 'foo-mod racket
  (define (bar n)
    (+ n n))
  (define foo (bar 1))
  (provide foo))

(require 'foo-mod)
foo

与您的示例相比,这是否简洁?当然不是。但在实践中,这种封装通常在模块粒度上工作得很好。

  • 例如,像 bar 这样的私有助手可能在定义多个其他函数或常量时很有用。

  • 通常文件形式更方便:如果像bar 这样的帮助器不是嵌套的,而是在foo.rkt 的模块顶层,则更容易在一个 REPL。

附言Racket 提供了一个define-package 形式来与 Chez Scheme 兼容,但它在 Racket 中不是惯用的;相反,你会使用一个子模块。

【讨论】:

    【解决方案2】:

    您的原始代码有语法错误,因为标识符的define 所需的语法是

    (define <identifier> <expression>)
    

    但你的语法是

    (define <identifier> <definition> <expression>)
    

    您需要一些方法来对&lt;definition&gt;&lt;expression&gt; 进行分组。您正在寻找的是允许词法定义的东西 - 在 Scheme 中,这是一个带有 &lt;body&gt; 的句法形式。其语法形式是lambda 或任何let(和变体)或“程序化”begin

    但是,这很容易在 Scheme 中完成,不需要 Racket 扩展或额外的空词法环境或 &lt;body&gt; 语法形式。只使用你认为“不满意”的东西

    (define foo
      (let ((bar (lambda (x) (+ x x))))
        (bar 1)))
    

    甚至

    (define foo
      ((lambda (x) (+ x x)) 1))
    

    过多的糖,甚至是语法糖,都会对健康造成不良后果...

    【讨论】:

    【解决方案3】:

    foo1 也等价于:

    (define foo1
      (let ()
        (define (bar n)
          (+ n n))
        (bar 1)))
    

    这对你来说更容易接受吗?

    【讨论】:

      【解决方案4】:

      回答您的问题:

      1. define 只能按照specification 的要求以特定方式使用。规范未涵盖您想要做的事情,因此会出现错误。如您所知,define 为表达式的值分配名称,只是您不能直接在其上下文中创建内部定义。
      2. 但还有其他表达式允许在此上下文中创建新绑定。恕我直言foo2 是这里的最佳选择,而且它也是惯用的。如果bar 是递归定义,您可以使用letrec

      但是,如果丢失一点语法糖会让您感到困扰(因为过程是在 let 表达式中定义的方式),那么请尝试使用 local,它会在 Racket 中工作:

      (define foo
        (local [(define (bar n) (+ n n))]
          (bar 1)))
      

      【讨论】:

        猜你喜欢
        • 2019-10-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-04
        • 2010-09-25
        • 1970-01-01
        • 1970-01-01
        • 2021-05-12
        相关资源
        最近更新 更多