Is it possible to "extend" the function / lambda / macro in a Scheme?

For example: if I want the function equal? recognized my own type or record, can I add a new equal? behavior equal? ? without erasing or overwriting the old?

Or, for example, if I want the "+" function to also accept a string?

+5
inheritance scheme r5rs r6rs r7rs
source share
5 answers

Instead of using import best solution is to keep track of the original function using let binding. It is also better to verify that the type of the argument is a string, not a number. Using both of these approaches means you can compose a technique.

 (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))))) 

The above function will create a + function that works with numbers, strings or vectors. In general, this is a more extensible approach.


I was able to verify that this works correctly in MIT / GNU Scheme, Guile, Racket, Chicken, TinyScheme and SCSH. However, in some implementations, such as Biwa Scheme, you must use set! instead of define . In Ikarus set! cannot be used for the imported primitive, and define ruin the environment, so you need to do this in two stages:

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

Note that according to R5RS , define and set! should be equivalent in this case:

At the top level of the program definition

 (define <variable> <expression>) 

has essentially the same effect as an assignment expression

 (set! <variable> <expression>) 

if <variable> is bound.

+3
source share

So far, solutions have worked less optimally in the R6RS / R7RS environment. I was thinking about generics when I started playing with this, but I didn't want to roll back my own type system. Instead, you provide a predicate procedure that should ensure that the arguments are appropriate for that particular procedure. It is not perfect, but works similarly to other R5RS answers, and you never redefine procedures.

I wrote everything in R6RS, but I think it is easy to port to R7RS. Here is an example:

 #!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) 

As you can see, I import + different name, so I do not need to redefine it (which is unacceptable).

Here is the library implementation:

 #!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))) 

As you can see, I use messaging to add additional methods.

+1
source share

The trick is to define your own extended function so that it distorts the standard function, but calls the standard function if necessary. Inside an extended function, you can do import to get a standard function. Here's the version + , which also accepts strings:

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

(This is a little careless, as it assumes that there is at least one argument, and it only checks the type of the first argument, but it illustrates the technique.)

0
source share

Not a clean scheme, but in Guile , for example, you can use CLOS - as an OO system:

 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 
0
source share

You cannot use

 (define + (let ((old+ +)) ...)) 

because define sets up a recursive environment for its init form. Thus, when evaluating + in (old+ +) it will be unbound. Thus:

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

The following works:

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

although it’s not so pretty.

0
source share

All Articles