pythonのデコレータについて

結構面白い。

def dec(fn):
    return lambda x, *arg, **kw : fn(x-1,*arg,**kw)

@dec
def f(n):
    print "input: %d" % n
    return 1  if n<=0 else n * f(n-1)

print f(10)
# input: 9
# input: 7
# input: 5
# input: 3
# input: 1
# input: -1
# 945

これは結局、関数を受け取って関数を返す関数を書けば良いだけなので

(*args -> **kw -> a) -> (*arg -> **kw -> a)

schemeで書くと以下のような感じ。

(define (dec$ fn)
  (lambda (x . args)
    (apply fn (- x 1) args)))

(define (f n)
  (if (<= n 1) 1 (* n (f (- n 1)))))

((dec$ f) 10) ; => 362880

嘘。これではうまくいかない。内部でdec$によって変換されたfが使える必要がある。

(define (dec$ fn)
  (lambda (x . args)
    (apply fn (- x 1) args)))

(define (f n)
  (print "input:" n)
  (if (<= n 1) 1 (* n (f (- n 1)))))
(update! f dec$) ;; (set! f (dec$ f))

(f 10)
;; input:9
;; input:7
;; input:5
;; input:3
;; input:1
;; 945

同じ挙動になった。ただこれはデコレータがない時のpythonの書き方と同じ感じ。デコレータをつくらないといけない。デコレータはひとつ下の関数にしか適用されないのだからこんな感じに。名前を取るようにするととても楽。

(define-syntax with-decorator
  (syntax-rules ()
    [(_ decorator name definition)
     (begin definition
            (update! name decorator)
            name)]))

(with-decorator dec$ g
  (define (g n)
    (print "input:" n)
    (if (<= n 1) 1 (* n (g (- n 1))))))

(g 10)
;; input:9
;; input:7
;; input:5
;; input:3
;; input:1
;; 945

デコレータは引数をとる形でもつかえた

たぶん以下のようになっているだけ。

*args -> (*args -> **kw -> a) -> (*args -> **kw -> a)

きっとこういう風に定義すればうまく動作する。

def natural(default):
    def _natural(fn):
        def __natural(x, *args, **kw):
            if x < 0:
                return default
            else:
                return fn(x, *args)
        return __natural
    return _natural

@natural(None)
def f(n):
    def g(n,acc):
        return acc if n <= 1 else g(n-1,acc*n)
    return g(n,1)

print f(10) # => 3628800
print f(-100) # => None

うまくいった。

実際のところ

with-decoratorとschemeのsyntaxはあまり合わなそう。でもメモ化などが手軽に定義できるので便利な機能ではある。