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).
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With