Is there a valid usecase for redefining “define” i

2019-05-06 06:45发布

问题:

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