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 want to bind the original define
to a different symbol, you can:
#lang racket
(require (rename-in racket (define mydef)))
(mydef n 2)
(mydef (times2 n) (* 2 n))
(times2 n)
=> 4
Now can redefine define
, but depending on what you're up to it's more likely you would end up defining it as a macro rather than a function. Inside your define
macro (or function) you can still use the orginial define
which is now bound to mydef
.
Redefining define
can make sense in some contexts. An alternative could be to work with something akin to a Python decorator. Here's an example for memoisation. Assuming this procedure:
(define (memoize fn)
(let ((cache (make-hash)))
(λ arg (hash-ref! cache arg (thunk (apply fn arg))))))
and a classical Fibonacci procedure:
(define fib
(lambda (n)
(if (< n 2) n (+ (fib (sub1 n)) (fib (- n 2))))))
(time (fib 35))
cpu time: 3039 real time: 3036 gc time: 0
9227465
can be rewritten
(define fib
(memoize
(lambda (n)
(if (< n 2) n (+ (fib (sub1 n)) (fib (- n 2)))))))
(time (fib 35))
cpu time: 1 real time: 0 gc time: 0
9227465