N2 ToolBox(跡地)

跡地です。引っ越しました。http://d.hatena.ne.jp/nosen

Scalaコピペ道場:java.io.Fileのラッパを作ってみた

2009-10-28 23:37:14 | Scala
ほんの思いつき、かつ ほかに誰かが既にやってそうですけど、作ってみました。
java.io.Fileのラッパです。

使い方はこんな感じ。


 1 import io.File._
 2 import io.IO
 3
 4 //カレントディレクトリ直下のsrcディレクトリ以下の
 5 //.scalaファイルを再帰的に検索
 6 val src = pwd / "src"
 7 val scalaFiles = find(src).filter(_.getName.endsWith(".scala"))
 8 for(f <- scalaFiles) {
 9     println(f.getName)
10 }
11
12 //見つけたScalaファイルを標準出力に出力
13 for(f <- scalaFiles) {
14     f > IO.Std
15
16 }
17
18 //ファイルのコピー
19 (pwd / "build.xml") > (pwd / "build.xml.bkp")
20
21 //build.xmlを標準出力に出力
22 (pwd / "build.xml") > IO.Std
23


"/" メソッドで子ファイルが取れるっていうのと、">"でファイルのコピーが出来るっていうがやりたかったんです。

それだけです。

でも、いろんな便利メソッドを追加していったら、もしかしたら便利かもしれない。。


ソースはこちら↓
http://hg.sourceforge.jp/view/scala-cp-src/main/file/c08b917a8886/src/main/io/File.scala


Ivyで複数のMavenリポジトリを使う

2009-10-26 21:18:58 | オープンソース
最近個人的にivyを使ってみてます。

Mavenよりは若干取っ付きにくいですが、慣れてくると自由が利いていいのかもしれません。
まだ慣れてないので、なんとも言えないのですが。。

さて、以下の設定はScalaTestとか、ibiblioでない独自のリポジトリを持っているライブラリを取ってくるために、
複数のMavenリポジトリを使うようにする、ivy-settings.xmlの設定例です。
自分用メモです。

 1 <ivysettings>
 2   <settings defaultResolver="default"/>
 3   <resolvers>
 4     <ibiblio name="ibiblio" m2compatible="true"/>
 5     <url name="scala-tools" m2compatible="true">
 6       <artifact pattern="http://scala-tools.org/repo-releases/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"/>
 7     </url>
 8     <chain name="public" returnFirst="true">
 9       <resolver ref="scala-tools"/>
10       <resolver ref="ibiblio"/>
11     </chain>
12   </resolvers>
13   <include url="${ivy.default.settings.dir}/ivysettings-shared.xml"/>
14   <include url="${ivy.default.settings.dir}/ivysettings-local.xml"/>
15   <include url="${ivy.default.settings.dir}/ivysettings-main-chain.xml"/>
16   <include url="${ivy.default.settings.dir}/ivysettings-default-chain.xml"/>
17 </ivysettings>



Scalaコピペ道場

2009-10-22 08:07:02 | Scala
ScalaのDIフレームワーク、改善しているうちにBlogに貼付けられる長さではなくなってきたので、
SourceForge にプロジェクトを作ってそこにコミットすることにしました。

ソースがこれで↓
http://hg.sourceforge.jp/view/scala-cp-src/main/file/3154e54d94e6/src/main/inject/Component.scala

仕様はこれ↓
http://hg.sourceforge.jp/view/scala-cp-src/main/file/3154e54d94e6/src/test/inject/ComponentSpec.scala

現在87行。。前回はまった共変の罠 問題は解決されています。

このSourceForgeのプロジェクトは、今回のように

Jarにして配布するまでもないけど、そこそこ再利用性のありそうなコードをコピペベースで利用して
もらうために配布するプロジェクト、というふれこみになっています。

名づけて

「Scalaコピペ道場」

まだ自分はScala修行中なので、「道場」ってつけてみました。
今後もよさげなコードがかけたら追加していきたいと思います。

ソースツリーごとチェックアウトしたい人は、Mercurialで

hg clone http://hg.sourceforge.jp/view/scala-cp-src/main/

で落とせます。


共変の罠

2009-10-17 10:15:12 | Scala
前回のエントリで

object A extends Key[A]
class A {
    def methodA = "a"
}

object B extends Key[B]

class B(inject:Injector) {
    val a = inject(A)
}

val component = Component.define(B, new B(_))
                         .define(A, new A)
val b = component(B)

こんな感じのサンプルを示したのですが、実は、これ、意図した通りに
コンパイラの型のチェックがかからないことが判明してしまいました。

val component = Component.define(B, new B(_))
                         .define(B, new A) //本当はエラー

これでもコンパイルが通ってしまうのです。第一引数がKey[B]なら第二引数は(Injector)=>B
でないとエラーになるようにしたかったのに。。

このとき、defineメソッドのシグネチャがどんな風になっているのかというと、
def define[T](key:Key[T], factory: (Injector)=> T) 
def define[T](key:Key[T], factory: => T) 

こんなふうになっていました。
問題はdefineの呼び出し時に明示的に型パラメータを指定していないことでした。
val component = Component.define[B](B, new B(_))
                         .define[A](B, new A) //コンパイルエラーになる

こうやって、型を明示的に指定してあげると、ちゃんとコンパイルエラーになります。
おそらく、型を指定しないと、このケースでは戻り値の型から型推論することができないので、
コンパイラは
val component = Component.define[Any](B, new B(_))
                         .define[Any](B, new A) 

こういう風に解釈しているのだと思われます。
そして、KeyもFunctionもTに関して共変な型であったがためにこれで
普通にコンパイル通ってしまっていたのかと。

Javaには共変、反変という概念がないので、こういうはまり方はScalaならではといえます。

いちいち型パラメータを明示的に指定しないといけない上に、
指定しないと中途半端にコンパイルが通ってしまうというのはよろしくないので、
対策を考えたいと思います。

Scalaによる64行のDependency Injectionフレームワーク

2009-10-14 01:58:33 | その他
機能のエントリで作ったDIフレームワークをもうちょっとちゃんとしました。
長いですけど引用します。
コンポーネントの循環参照を自動的に解決できるようになりました!


 1 import scala.collection.immutable.Map
 2 import scala.collection.immutable.Stack
 3
 4 trait Key[+T]
 5
 6 class Component(val entries:Map[Key[Any],(Injector)=>Any]) {
 7
 8     def define[T](key:Key[T], factory: (Injector)=> T) =
 9         new Component(entries + (key -> factory))
10
11     def define[T](key:Key[T], factory: => T) =
12         new Component(entries + (key -> (inj => factory)))
13
14     def apply[T](key:Key[T]):T = {
15         val inj = Injector.root(this)
16         inj(key)
17     }
18
19 }
20
21 object Component {
22     def define[T](key:Key[T], factory: (Injector)=> T) =
23         new Component(Map(key -> factory))
24
25     def define[T](key:Key[T], factory: => T) =
26         new Component(Map(key -> (inj => factory)))
27 }
28
29 object Injector {
30     def root(component:Component) = new Injector(component, Stack.Empty)
31 }
32
33 class Injector(val component:Component, resolving:Stack[Dependency[Any]]) {
34     def push(dep:Dependency[Any]) = new Injector(component, resolving push dep)
35
36     def apply[T](key:Key[T]):T = {
37         if(!component.entries.contains(key)) error("dependency not found:" + key + ",path=" 
38             + resolving.map(_.key).mkString("->"))
39
40         resolving.find(dep=>(dep.key == key)) match {
41             case Some(d) => new DependencyDependency(key, d)
42             case None => new ObjectDependency(key, this)
43         }
44     }
45 }
46
47 trait Dependency[+T] {
48     val key:Key[T]
49     def instance:T
50 }
51
52 class ObjectDependency[+T](val key:Key[T], inj:Injector) extends Dependency[T] {
53     private val injector = inj push this
54     private val factory = inj.component.entries(key)
55     val instance:T = factory(injector).asInstanceOf[T]
56 }
57
58 class DependencyDependency[+T](val key:Key[T], dep:Dependency[Any]) extends Dependency[T] {
59     def instance:T = dep.instance.asInstanceOf[T]
60 }
61
62 object Dependency {
63     implicit def dependencyToComponent[T](dep:Dependency[T]):T = dep.instance
64 }


使い方はこんな感じ。


 1 class A(val inject:Injector) {
 2    val b = inject(B)
 3    def sayHello = b.c.sayHello
 4 }
 5 object A extends Key[A]
 6
 7 class B(val inject:Injector) {
 8     val c = inject(C)
 9 }
10
11 object B extends Key[B]
12
13 class C(val inject:Injector) {
14     val a = inject(A)
15
16    def sayHello = println("hello, I am C")
17 }
18
19 object C extends Key[C]
20
21 val component:Component = Component
22   .define(B, new B(_))
23   .define(A, new A(_))
24   .define(C, new C(_))
25
26 val a = component(A)
27
28 a.sayHello
29


A -> B -> C -> A という循環参照が解決されてます。

Scalaによる19行のDependency Injectionフレームワーク

2009-10-12 10:32:09 | その他
お久しぶりです。

Scala でDIっぽいことをやる方法と申しますと、こういったアプローチがあるのですが、
これだと、inject用のTraitを山のように定義しないといけなくて、煩雑な気がしたので、自分で簡単なDIの仕組みを作ってみました。

こんな感じです↓


 1 import scala.collection.immutable.Map
 2
 3 trait Key[+T]
 4
 5 class Component(entries:Map[Key[Any],()=>Any]) {
 6
 7     def define[T](key:Key[T], instance: => T) =
 8         new Component(entries + (key -> (()=>instance)))
 9
10     def apply[T](key:Key[T]):T =
11         instance(entries(key)).asInstanceOf[T]
12
13     private def instance[T](factory:()=>T):T =  factory()
14 }
15
16 object Component {
17     def define[T](key:Key[T], instance: => T) =
18         new Component(Map.empty + (key -> (()=>instance)))
19 }


やってみたら、なんとたった19行で出来ちゃいました。
まぁ、エラー処理とかちゃんとやるともうちょい長くなりますけどね。。
あと、11行目とか、なぜこれでコンパイルエラーにならないのか自分でもわかってないです。
キャストしようとしても T がなにかわからないはずなのに。
まだまだ勉強がたりないですね。

使い方はこんなです↓


 1 //コンポーネント
 2 class A(inject:Component) {
 3     val b = inject(B)
 4
 5     def sayHello {
 6         b.sayHello
 7     }
 8 }
 9
10 object A extends Key[A]
11
12 class B(inject:Component) {
13     val c = inject(C)
14
15     def sayHello {
16       c.sayHello
17     }
18
19 }
20
21 object B extends Key[B]
22
23 class C {
24     def sayHello {
25       println("Hello, I am C")
26     }
27 }
28
29 object C extends Key[C]
30
31 //コンポーネントをくっつける定義
32 val component:Component  =
33     Component.define(A, new A(component))
34              .define(B, new B(component))
35              .define(C, new C)
36
37
38 //使ってみよう
39 val a = component(A)
40
41 a.sayHello
42


39行目で クラスA のインスタンスが生成されます。

A の sayHello が Bの sayHello を呼び出し、B のsayHelloが cのsayHelloを呼び出すので、
このコードを実行すると「Hello, I am C」と出力されます。

ポイントは、コンパニオンオブジェクトがコンポーネントのキーになってるとこですかね。

Componentクラスは、コンパニオンオブジェクトをキー、対応するクラスのインスタンスを生成する関数を値とするMap で、
他のコンポーネントに依存するコンポーネントは、このComponentクラスのインスタンスを保持して
自分を初期化するときに、Component クラスから必要なコンポーネントのインスタンスを取得しています。

Scala では、なるべくコンパイラに仕事をさせて、ランタイムエラーが発生しないようなコードを書くのが良いと
思っているのですが、この例では必要なコンポーネントが見つからなかったときにランタイムエラーが発生してしまいます。
というデメリットはあるのですが、これだけ単純な仕掛けでコードがぐっと簡潔になるので、
それを補うだけのメリットもあるのではないかと思います。