问题
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).
回答1:
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.
回答2:
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.
回答3:
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
来源:https://stackoverflow.com/questions/23170706/is-there-a-valid-usecase-for-redefining-define-in-scheme-racket