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