Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 448
Emil H Avatar asked Apr 19 '14 13:04

Emil H


2 Answers

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.

like image 101
Chris Jester-Young Avatar answered Sep 23 '22 20:09

Chris Jester-Young


The other two answers have already provided excellent explanations, so I'll just add a more Racket-specific example.

In Racket, you can build your own #lang that treats definitions differently than the base Racket language. For example, Typed Racket's definition form allows code that looks like this:

(define (fact [n : Integer]) : Integer
  (if (zero? n)
      1
      (* n (fact (sub1 n)))))

This define form allows extra type annotations for communicating with Typed Racket's typechecker. Without being able to override core forms in a #lang, it wouldn't be possible to seamlessly add type annotations.

like image 23
Asumu Takikawa Avatar answered Sep 24 '22 20:09

Asumu Takikawa