在方案中定义(指向)过程

Define (pointers to) procedures in scheme

我是 scheme 的新手,正在尝试了解创建过程时 define 的工作原理。更准确地说是以下两个声明之间的区别:

(define name (procedure))
(define (name) (procedure))

有人可以提示 define 如何工作 'under the hood' 吗?我知道在第一种情况下 define 创建一个指向过程的指针。第二个是没有参数的过程的精简版本 (define (name arg) (procedure)),但是指针 name(name) 有何不同?

显然,name 的使用取决于它是如何定义的。

> (define proc (+ 1 1))
> proc
2
> (proc)
. . application: not a procedure;


> (define (proc) (+ 1 1))
> proc
#<procedure:proc>
> (proc)
2

谢谢!

define 形式是“语法糖”:它只是一种更方便的语法。表达式

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

根据定义等同于

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

也就是说,正如 (define bar 1) 设置 bar 等于 1(define foo (lambda ...)) 定义 foo 等于 value (lambda (x) (+ x 1)),它是一个 procedure(即函数),它将其单个参数加 1。

啊哈!这里的时刻是意识到在 Scheme(和其他 Lisps)中 过程是一个值 ,具有与 1"hello" 相同的状态,因此可以分配到 foo 等符号。 表达式 (lambda ...) 的计算结果为过程类型的值。 把这些弄清楚,语法 (define (foo ...) ...) 应该是透明的。

(不要想到“函数指针”——那是 C 的概念,在这里只会混淆)。

(define (proc) ...) 只是 (define proc (lambda () ...)).

的语法糖

如果您熟悉命令式语言,define 确实是一个赋值:

> (define proc 2)

<=>(左右)

proc = 2;

可以分配给符号的一件事是函数,如下:

> (define proc (lambda (x y) (+ x y)))
> (proc 1 2)
3

<=>(左右)

>>> proc = lambda x, y: x + y
>>> proc(1, 2)
3

现在没有什么能阻止您将函数定义为不带参数:

> (define proc (lambda () (+ 1 1)))
> (proc)
2

函数 赋值给 proc,不带任何参数,并返回 (+ 1 1) 的值(即 2)。这与(define proc (+ 1 1))不同,它直接将值赋给proc(实际上完全等同于(define proc 2))。

现在,可能会让您感到困惑的是,由于编写 lambda 工作量太大,Scheme 实现通常会提供 'short-hand' 版本的函数定义:

> (define (proc <args>) <body>)
<=>
> (define proc (lambda <args> <body>))

所以 (define (proc) 2) 脱糖到 (define proc (lambda () 2)),而 (define proc 2) 只是将 2 分配给 proc。符号可能通过指针绑定到值这一事实是一个只与您的解释器有关的实现细节,应该与推理无关。

TL;博士:

(define (func) x) 

创建函数

(define func x)

x 分配给 func

这些都不是任何方式、形状或形式的指针。

define 绑定当前环境中的名称和值,在Scheme中,过程是first-class值。

(define name (procedure))

name 定义为立即调用 procedure 的结果值。

(define (name) (procedure))

name 定义为不带参数的过程,并且在调用时 returns 不带参数调用 procedure 的结果。
第二种形式相当于

(define name (lambda () (procedure)))

示例(来自 Racket - current-seconds 是自 1970 年 1 月 1 日午夜 UTC 以来的秒数):

> (current-seconds)
1462879945
> (define name (current-seconds))
> name
1462879957

name 是数字,不是程序。

> (define (nameproc) (current-seconds))
> nameproc
#<procedure:nameproc>

nameproc是程序,不是数字。

> (nameproc)
1462879980
> (nameproc)
1462879983

如您所见,nameproc returns 每次调用时都有不同的值...

> name
1462879957
> name
1462879957

...但是 name 是一个整数并且不会改变。

define 可能是 Scheme 中最模糊的特殊形式,这是因为它在不同的范围和不同的第一个参数中有不同的用途。这里是:

符号和列表第一个参数的区别

define的基本用法是:

(define var <expression>)

它用表达式 <expression> 的结果定义变量 var

由于 Scheme 是一个 LISP1 过程(在其他一些语言中也称为函数)是使用评估的 lambda 形式创建的,例如。 (lambda argument-list . body) 您可以通过 define:

为过程指定名称
(define add1 (lambda (n) (+ 1 n)))

对于命名过程,define 有一个特殊的语法,其中 definelambda 形式混合在一起。您可以将 add1 写为:

(define (add1 n) (+ 1 n))

它的意思完全一样,有些书不向用户公开最后一个版本,因为有两种方法更容易混淆,特别是如果你想要一个过程到 return 一个过程。

顶级或词法范围

当顶级值分配给全局范围时。这意味着所有过程都将能够访问它,除非变量在本地范围内重新定义,查看从使用到顶层的代码。

如果define发生在过程或过程的语法糖中(letlet*letrecreclambda) 则该表单与过程顶部的 letrec 相同,并且它仅存在于该过程和嵌套过程中。第二个 letrec 中的代码完成 运行 绑定消失:

(define (test n)
  (define v (* 2 n))
  (define (add1 n) (+ 1 n))

  (add1 v))

这等同于:

(define test 
  (lambda (n)
    (letrec ((v (* 2 n))
             (add1 (lambda (n) (+ 1 n)))
      (add1 v))))

letrec 只是一个幻想 let

(define test 
  (lambda (n)
    (let ((v 'undefined) (add1 'undefined))
      (let ((v1 (* 2 n))
            (v2 (lambda (n) (+ 1 n))))
        (set! v v1)
        (set! add1 v2))
      (add1 v))))

当然 let 只是立即应用的 lambda

(define test 
  (lambda (n)
    ((lambda  (v add1)
       ((lambda (v1 v2)
          (set! v v1)
          (set! add1 v2)) (* 2 n) (lambda (n) (+ 1 n)))

       (add1 v) ; can access n, v, and add1 but not v1, and v2 
       'undefined 'undefined)))

define 仅在名称和值之间进行绑定,其中值通常表示对象的地址(也称为指针)。同一个名字在不同的范围内可以有不同的绑定,所以同一个变量名并不总是指向同一个对象。这是一个例子:

(define lst '(1 2 3 4 5))
(define (last x)
  (define lst (cdr x))
  (if (null? lst)
      (car lst)
      (last lst)))

(last lst) ; ==> 5

此处的顶级 lst 始终是 '(1 2 3 4 5),而过程中的 lst 始终是参数 x 的子列表之一。它们以相同的名称同时存在,但都指向不同的值。在正文中,全局不可访问,因为它被具有相同名称的本地绑定遮蔽了。