2012-03-16

クロージャー?

Twitterで呟いたが、Javaの無名クラスは厳密にはクロージャーになりえず、JDK8辺りで入る予定のlambdaも無名クラスの糖衣構文になるとかという話を昨日バーで同僚としていた。
糖衣構文でもあれば便利だろうなぁと思う場面が山ほどあるので、是非早いとこ実装してもらいたいが(JDK7では見送られた)。
話に参加していたもう一人の同僚が、違いが分からないと言っていたので、関数型言語のクロージャーとどう違うのかというのでも書いてみようかなぁと。ただ、僕自身あんまり理解してないので間違いは山ほどあるかと・・・

そもそも、クロージャーとは何ぞやと。詳しい説明はWikipediaでも見てもらうとして、上記で議論になっていたのは捕捉時の環境が閉じているかどうかということであった。JavaやC++ではここが完全に閉じることができず、渡された自由変数が参照渡しになる。以下はJavaでそれっぽく見せるようにしたコード。(C++では補足さえ出来ない)
package closure;

public interface Lambda<T> {

 T apply(Object... args);
}

// 別ファイル
package closure;

public class Closure {

 private Object obj;
 
 public void run() {
  // property
  Lambda<Object> lam = new Lambda<Object>() {
   @Override
   public Object apply(Object... args) {
    obj = args[0];
    System.out.println(obj);
    return obj;
   }
  };
  final Object obj2 = (Object)"string";
  Lambda<Object> lam2 = new Lambda<Object>() {
   @Override
   public Object apply(Object... args) {
    // obj2 = args[0]; // *1 compile error
    System.out.println(obj2);
    return null;
   }
  };
  System.out.println(lam.apply("here we go"));
  System.out.println(obj);
  lam2.apply();
 }
 
 public static void main(String[] args) {
  Closure cl = new Closure();
  cl.run();
  
 } 
}
*1の部分が重要で、Javaでは無名クラスの中から参照できる自由変数(とここでは呼ぶ)は変更不可能でなければならない。多分JVMの実装上も問題だろう。lam2がrunから戻った際にスタックからobj2が消えるのでその辺が問題になると思われる。
ではわれらがSchemeではどうだろうか?
(define obj #f)

(define (runner lam1 lam2)
  (print (lam1 "here we go"))
  (print (lam2 "we can do it")))

(define (main args)
  (let* ((obj2 "string")
  (lam1 (lambda (o) (set! obj o) o))
  (lam2 (lambda (o) (set! obj2 o) o)))
    (runner lam1 lam2)
    (print obj #\: obj2)))
#|
here we go
we can do it
here we go:we can do it
|#
あまりいい例ではない気もするが、lam2で捕捉されたobj2の値が変更されるとlambdaの外側のでも問題なく変更されている。
これが出来て何が嬉しいかと言われると正直微妙ではある。(結構使うけどこういうの)
ただ、Javaでもfinalつければ捕捉出来るんだし、ほぼ同じじゃないのだろうか?(いい加減)
(define obj #f)

(define (runner o run?)
  (let* ((obj2 o)
  (lam1 (lambda (o) (set! obj o) o))
  (lam2 (lambda (o) (set! obj2 o) o)))
    (cond (run?
    (print (lam1 "here we go"))
    (print (lam2 "we can do it"))
    (print obj #\: obj2))
   (else
    (values lam1 lam2 obj2)))))

(define (main args)
  (let ((o "string"))
    (runner o #t)
    (receive (lam1 lam2 obj2) (runner o #f)
      (print (lam1 "here we go"))
      (print (lam2 "we can do it"))
      (print obj #\: obj2))
    (print o)))
#|
here we go
we can do it
here we go:we can do it
here we go
we can do it
here we go:string
string
|#
こっちの方が捕捉時の環境というのが捕らえやすいだろうか?あんまり変わらないか?

No comments:

Post a Comment