5

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

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

Felipe
  • 16,649
  • 11
  • 68
  • 92
  • 2
    This question becomes more and more interesting, specifically since you tagged [tag:r7rs]. I would have liked a [tag:r6rs] tag as well. I'd bet you'll need to roll your own generics or something in those. – Sylwester May 27 '14 at 21:41
  • Just added r6rs as you suggested. Thank you. – Felipe May 27 '14 at 23:52

6 Answers6

4

Rather than using import, a better solution is to keep track of the original function by let-binding it. It's also better to check that the type of the argument is a string, rather than that it is not a number. Using both of these approaches means that it's possible to compose the 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 will produce a + function that works on numbers, strings, or vectors. In general, this is a more extensible approach.


I was able to verify that the above works correctly in MIT/GNU Scheme, Guile, Racket, Chicken, TinyScheme, and SCSH. However, in some implementations, such as Biwa Scheme, it is necessary to use set! instead of define. In Ikarus, set! cannot be used on an imported primitive, and define messes up the environment, so it is necessary to do this in two steps:

(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! are supposed to be equivalent in this case:

At the top level of a program, a definition

(define <variable> <expression>)

has essentially the same effect as the assignment expression

(set! <variable> <expression>)

if <variable> is bound.

Lily Chung
  • 2,919
  • 1
  • 25
  • 42
  • Wouldn't old+ be undefined? – Sylwester May 27 '14 at 07:04
  • @Sylwester Look again- it's bound in the `let`. – Lily Chung May 27 '14 at 14:42
  • A note on Racket. None of the approaches work by default. E.g. `#!racket ; ==> module: duplicate definition for identifier in: +` and `#!r6rs ; ==> module: identifier is already imported in: +`. It works in racket in legacy language R5RS (not #!r5rs module language) with the special setting "Disallow redefinition of initial binding" unchecked. In Ikarus you'll get exception `multiple definitions of identifier` if run with `--r6rs-script`. REPL is probably more forgiving but that won't help if you are using it for a project. – Sylwester May 27 '14 at 21:39
  • @Sylwester I did not understand your commend very well. This solution works on r6rs and also on r7rs? – Felipe May 27 '14 at 23:53
  • @FelipeMicaroniLalli I tested for R6RS on both ikarus and racket and this does not work. I did manage to get it to work on r5rs legacy mode in racket. In Chibi-scheme (The only R7RS implementation) `define` is a `letrec` making `+` undefined at the time of the `old+` binding (like I predicted in my first comment), but the second version that uses a temporary symbol `new+` works. – Sylwester May 28 '14 at 00:25
  • @Sandra Probably not? But idk, I wrote that post 7 years ago. – Lily Chung Apr 21 '21 at 08:52
1

So far the solutions work less than optimal in an R6RS / R7RS environment. I was thinking generics when I started playing around with this, but I didn't want to roll my own type system. Instead you supply a predicate procedure that should ensure the arguments are good for this particular procedure. It's not perfect but it works similar to the other R5RS answers and you never redefine procedures.

I've written everything in R6RS but I imagine it's easily ported to R7RS. Here's 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 + by a different name so I don't need to redefine it (which is not allowed).

Here's 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 message passing to add more methods.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
0

The trick is to define your own, extended function so it shadows the standard function but calls the standard function when it's needed. Inside your extended function, you can do an import to get at the standard function. Here's a version of + that also accepts strings:

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

(This is a little sloppy in that it assumes there is at least one argument and it only checks the type of the first argument. But it illustrates the technique.)

Ben Kovitz
  • 4,920
  • 1
  • 22
  • 50
  • This fails if you want to use the same approach to extend the function to another type. – Lily Chung May 27 '14 at 01:09
  • 1
    Good point. Your answer is much better. I'm leaving mine up, just so people can see how _not_ to do it. – Ben Kovitz May 27 '14 at 01:11
  • Actually, one thing this is good for is extending `lambda` or a macro, since those can't be bound in a `let`. By extending `lambda`, I don't mean extending _a_ `lambda`, but redefining what `lambda` itself means in general, by extending the standard definition of `lambda`. It would be better to use a technique that allows chaining to whatever the previous definition of `lambda` was. Is there a way to do that? – Ben Kovitz May 27 '14 at 19:43
  • 1
    AFAICT the only way is in two steps. For instance, to redefine `lambda` to display the string "hello" every time a function defined with it is called: `(define-syntax old-lambda lambda)`, then `(define-syntax lambda (syntax-rules () ((_ (args ...) body ...) (old-lambda (args ...) (display "hello") body ...)) ((_ args body ...) (old-lambda args (display "hello") body ...))))` **Edit:** actually, that one won't chain, anyway. The issue is that macros are expanded "lazily", so it just goes into an infinite loop if you try to chain it. – Lily Chung May 27 '14 at 20:36
  • Will the next extension of `lambda` trash the preceding extension's `old-lambda`? **Edit**: Oops, that looks like a yes. – Ben Kovitz May 27 '14 at 20:41
  • 1
    I asked a [new question](http://stackoverflow.com/questions/23898908/extensible-macro-definitions) on this subject, since the discussion's getting a little too complex for commnets (and also since I don't know the answer). – Lily Chung May 27 '14 at 21:06
0

Not pure Scheme, but in Guile for example you can use the CLOS-like 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
uselpa
  • 18,732
  • 2
  • 34
  • 52
0

You can't use

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

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

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

The following works:

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

although it is not so pretty.

GoZoner
  • 67,920
  • 20
  • 95
  • 145
  • 1
    Interesting... it works for me on MIT/GNU Scheme (r5rs). Where in the docs does it say that `define` makes a recursive environment? – Lily Chung May 27 '14 at 14:46
  • @IstvanChung: Pretty sure this wont work in R6RS or R7RS. You can't redefine an imported binding. – leppie May 27 '14 at 17:02
  • @leppie *Note: please put your comments regarding my answer on my answer itself.* The question is also tagged [tag:r5rs]. In addition, there appears to be some way to do it in pretty much every implementation, though it takes a couple steps in some. – Lily Chung May 27 '14 at 17:07
  • @IstvanChung: I was replying at your comment, not your answer (and more of inline with this answer). – leppie May 27 '14 at 17:10
  • R6RS, specifically Ikarus. – GoZoner May 27 '14 at 19:40
  • @IstvanChung After reading the whole debate I'm confusing which question to mark as correct. Can you guys please edit the answer and let more clear if all this works on r5rs, r6rs and/or r7rs? I added the r6rs tag as well as suggested. Thank you, I'll wait more before mark the answer as correct. – Felipe May 27 '14 at 23:59
  • 2
    @FelipeMicaroniLalli As far as I can tell, it doesn't necessarily work under r6rs and r7rs. However, it's unclear whether any solution could possibly work, as there are restrictions on redefining imported bindings. – Lily Chung May 28 '14 at 00:09
0

In R7RS-large (or in any Scheme, really), you can use SRFI 128 comparators, which package up the ideas of equality, ordering, and hashing, in order to make generic comparisons possible. SRFI 128 allows you to create your own comparators and use them in comparator-aware functions. For example, <? takes a comparator object and two or more objects of the type associated with the comparator, and returns #t if the first object is less than the second object in the sense of the comparator's ordering predicate.

John Cowan
  • 1,497
  • 12
  • 13