Is there a valid usecase for redefining “define” in scheme/racket?

和自甴很熟 提交于 2019-12-05 12:55:17

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!