Search code examples
schemeracketlanguage-features

Is there a valid usecase for redefining "define" in scheme/racket?


I'm playing around with racket/scheme and it allows me to redefine for instance define and bind it as a value.

> (define define 2)
> define
2

In that scope I can no longer define anything using define since it is obviously bound to 2. This works for all "keywords" I tried it with (if, cond etc.).

However it is not possible to use define to specify my own definition function:

> (define mydef define)
stdin::14: define: not allowed in an expression context in: define

 === context ===
/usr/share/racket/collects/racket/private/norm-define.rkt:8:4: normalize-definition
/usr/share/racket/collects/racket/private/kw.rkt:796:2
/usr/share/racket/collects/racket/private/misc.rkt:87:7

I suppose there is another means of extending the language in racket to add my own definition function should I want to, but why is this way disallowed?

This does leave me wondering if there is any valid use case at all for redefining define? I realize that this is a bit opinion based, but I'm looking for use cases where this might be a justified thing to do (whether it is or not, is another matter).


Solution

  • Yes, you might actually want to extend the define form to provide capabilities that the standard define doesn't. An example is providing decorators (thanks to uselpa's answer for inspiration):

    (require (only-in racket/base (define basic-define)))
    
    (define-syntax wrap-decorators
      (syntax-rules ()
        ((_ () value)
         value)
        ((_ (decorator next ...) value)
         (decorator (wrap-decorators (next ...) value)))))
    
    (define-syntax define
      (syntax-rules (@)
        ((_ (@ decorator ...) (id . params) body ...)
         (define (@ decorator ...) id (lambda params body ...)))
        ((_ (@ decorator ...) id value)
         (define id (wrap-decorators (decorator ...) value)))
        ((_ other ...)
         (basic-define other ...))))
    
    (define (trace label)
      (lambda (f)
        (lambda args
          (dynamic-wind (thunk (eprintf "enter ~a: ~s~%" label args))
                        (thunk (apply f args))
                        (thunk (eprintf "exit ~a: ~s~%" label args))))))
    

    Now you can use it this way:

    (define (@ (trace 'hypot)) (hypot x y)
      (sqrt (+ (sqr x) (sqr y))))
    

    This causes the hypot function to be wrapped with trace so when you call it, tracing happens:

    > (hypot 3 4)
    enter hypot: (3 4)
    exit hypot: (3 4)
    5
    

    Or, using uselpa's memoize function, you can use:

    (define (@ memoize) (fib n)
      (if (< n 2)
          n
          (+ (fib (sub1 n)) (fib (- n 2)))))
    

    and get a speedy memoised fib function. You can even trace and memoise it, showing only the actual (cache miss) invocations:

    (define (@ (trace 'fib) memoize) (fib n)
      (if (< n 2)
          n
          (+ (fib (sub1 n)) (fib (- n 2)))))
    

    Notice, in my macro, that I imported Racket's define as basic-define, so that my redefined define could delegate to it.