Inheritance 有没有可能;扩展;方案中的函数/lambda/宏?

Inheritance 有没有可能;扩展;方案中的函数/lambda/宏?,inheritance,scheme,r5rs,r6rs,r7rs,Inheritance,Scheme,R5rs,R6rs,R7rs,例如:如果我想让函数equal?识别我自己的类型或记录,我可以添加equal?的新行为吗?不删除或覆盖旧的 或者,如果我想让函数“+”也接受字符串?诀窍是定义您自己的扩展函数,使其隐藏标准函数,但在需要时调用标准函数。在扩展函数中,您可以执行导入以获取标准函数。下面是一个版本的+,它也接受字符串: (define + (lambda args (if (number? (car args)) (let () (import (scheme))

例如:如果我想让函数
equal?
识别我自己的类型或记录,我可以添加
equal?
的新行为吗?不删除或覆盖旧的


或者,如果我想让函数
“+”
也接受字符串?

诀窍是定义您自己的扩展函数,使其隐藏标准函数,但在需要时调用标准函数。在扩展函数中,您可以执行
导入
以获取标准函数。下面是一个版本的
+
,它也接受字符串:

(define +
  (lambda args
    (if (number? (car args))
        (let ()
          (import (scheme))
          (apply + args))
        (apply string-append args))))

(这有点草率,因为它假设至少有一个参数,并且只检查第一个参数的类型。但它说明了这项技术。)

与其使用
导入
,不如通过
让它绑定来跟踪原始函数。最好检查参数的类型是字符串,而不是数字。使用这两种方法意味着可以组合该技术

(define +
  (let ((old+ +))
    (lambda args
      (if (string? (car args))
          (apply string-append args)
          (apply old+ args)))))

(define +
  (let ((old+ +))
    (lambda args
      (if (vector? (car args))
          (apply vector-append args)
          (apply old+ args)))))
上述操作将生成一个处理数字、字符串或向量的
+
函数。一般来说,这是一种更具扩展性的方法


我能够在麻省理工学院/GNU方案、诡计、球拍、鸡肉、TinyScheme和SCSH中验证上述方法是否正确。然而,在一些实现中,例如Biwa方案,有必要使用
set而不是
定义
。在Ikarus中,
set不能用于导入的原语,并且
定义
会弄乱环境,因此有必要分两步执行此操作:

(define new+
  (let ((old+ +))
    (lambda args
      (if (string? (car args))
          (apply string-append args)
          (apply old+ args)))))
(define + new+)

请注意,根据,
定义
设置应该是等效的:

在程序的顶层,定义

(define <variable> <expression>)
(定义)
本质上与赋值表达式具有相同的效果

(set! <variable> <expression>)
(设置!)
如果
已绑定

不是纯方案,但在示例中,您可以使用类似OO的系统:

scheme@(guile-user)> (use-modules (oop goops))
scheme@(guile-user)> (define-method (+ (x <string>) ...) (string-append x ...))
scheme@(guile-user)> (+ "a" "b")
$1 = "ab"
scheme@(guile-user)> (+ "a" "b" "c")
$2 = "abc"
scheme@(guile-user)> (+ 1 2)
$3 = 3
scheme@(guile-user)> (+ 1 2 3)
$4 = 6
scheme@(guile user)>(使用模块(oop-goops))
scheme@(guile user)>(定义方法(+(x)…)(字符串附加x…)
方案@(欺诈用户)>(+“a”“b”)
$1=“ab”
方案@(欺诈用户)>(+“a”“b”“c”)
$2=“abc”
方案@(欺诈用户)>(+12)
$3 = 3
方案@(欺诈用户)>(+1 2 3)
$4 = 6
您不能使用

(define +
  (let ((old+ +))
    ...))
因为
define
为其init表单设置了一个递归环境。因此,当在
(旧++)
中计算
++
时,它将被解除绑定。因此:

> (define + 
   (let ((old+ +))
     (lambda (a b) (display "my+") (old+ a b))))
Unhandled exception
 Condition components:
   1. &undefined
   2. &who: eval
   3. &message: "unbound variable"
   4. &irritants: (+)
以下工作:

> (define old+ +)
> (define + (lambda (a b) (display "my+\n") (old+ a b)))
> (+ 1 2)
my+
3

虽然它不是那么漂亮。

到目前为止,解决方案在R6RS/R7RS环境中的效果并不是最佳的。当我开始玩这个游戏时,我正在考虑泛型,但我不想使用我自己的类型系统。相反,您提供了一个谓词过程,该过程应确保参数适合于此特定过程。它并不完美,但它的工作原理与其他R5RS答案类似,而且您永远不会重新定义过程

我已经用R6RS写了所有东西,但我想它很容易移植到R7RS。 下面是一个例子:

#!r6rs

(import (sylwester generic)
        (rename (rnrs) (+ rnrs:+))
        (only (srfi :43) vector-append))

(define-generic + rnrs:+)
(add-method + (lambda x (string? (car x))) string-append)
(add-method + (lambda x (vector? (car x))) vector-append)
(+ 4 5)                ; ==> 9
(+ "Hello," " world!") ; ==> "Hello, world!"
(+ '#(1) '#(2))        ; ==> #(1 2)
如您所见,我使用不同的名称导入
+
,因此不需要重新定义它(这是不允许的)

以下是库实现:

#!r6rs

(library (sylwester generic)         
  (export define-generic add-method)
  (import (rnrs))

  (define add-method-tag (make-vector 1))

  (define-syntax define-generic
    (syntax-rules ()
      ((_ name default-procedure)
       (define name 
         (let ((procs (list (cons (lambda x #t) default-procedure))))
           (define (add-proc id pred proc)
             (set! procs (cons (cons pred proc) procs)))

           (add-proc #t
                 (lambda x (eq? (car x) add-method-tag))
                 add-proc)
           (lambda x
             (let loop ((procs procs))
               (if (apply (caar procs) x)
                   (apply (cdar procs) x)
                   (loop (cdr procs))))))))))

  (define (add-method name pred proc)
    (name add-method-tag pred proc)))

正如您所看到的,我使用消息传递来添加更多的方法。

在R7RS large(或者在任何方案中,实际上)中,您可以使用SRFI 128比较器,它将相等、排序和散列的思想打包在一起,以使通用比较成为可能。SRFI 128允许您创建自己的比较器,并在比较器感知功能中使用它们。例如,
如果您想使用相同的方法将函数扩展到另一种类型,则此操作将失败。这一点很好。你的答案好多了。我把我的留给大家,只是为了让大家知道如何不去做。事实上,这对扩展
lambda
或宏有好处,因为它们不能绑定在
let
中。通过扩展
lambda
,我不是指扩展
lambda
,而是通过扩展
lambda
的标准定义,重新定义
lambda
本身的一般含义。最好使用一种技术,允许链接到以前定义的
lambda
的任何内容。有没有办法做到这一点?唯一的办法是分两步。例如,要重新定义
lambda
以在每次调用使用它定义的函数时显示字符串“hello”:
(定义语法旧lambda lambda)
,然后
(定义语法lambda(语法规则)((((args…)body…)旧lambda(args…(显示“hello”)body…)(((args body…)旧lambda args(显示)body…)“hello”)body…)
Edit:实际上,这一个不会链接。问题是宏是“惰性地”扩展的,所以如果你尝试链接它,它就会进入无限循环。我问了一个关于这个问题的问题,因为讨论对于commnet来说有点太复杂了(而且我也不知道答案)。old+不是未定义的吗?@Sylwester再次查看-它被绑定在
let
中。Racket上有一个注释。默认情况下,所有方法都不起作用。例如
#!Racket;==>模块:标识符在+
#r6rs;=>模块:标识符已导入到+
中。它在旧语言R5RS的Racket中工作(不是#!r5rs模块语言)具有特殊设置“不允许重新定义初始绑定"未选中。在Ikarus中,如果使用
--r6rs脚本
运行,您将得到异常
标识符的多个定义
。REPL可能更宽容,但如果您将其用于项目,这将不会有帮助。@Sylvester我不太理解您的建议。此解决方案适用于r6rs,也适用于r7rs?@FelipeMicaroniLalli我测试了f或者在ikarus和racket上都使用R6RS,但这不起作用。我确实设法在r5rs legac上使用了它