Scala と Ruby で既存のクラスにメソッドを追加する

Written by @dr_taka_n at 2010/08/29 19:51:50 [, ]

既存のクラス、または、third party のライブラリにメソッドを追加したい時がある。 Ruby と Scala で実現してみる。

例として、文字列のストリームから次の行の文字列を取得するメソッドを追加してみることにする。

Ruby は Open Class

Ruby の場合だと、Open Class を使って容易に実現できる。標準ライブラリにある StringIO で実施してみる。

>> sio = StringIO.new("line one\nline two")
#<StringIO:0x3cc670>
>> sio.next_line
NoMethodError: undefined method `next_line' for #<StringIO:0x3cc670>
        from (irb):2

まだ実装していないので、当然ながらエラーとなる。

>> class StringIO
>>   def next_line
>>     line = gets
>>     line.chomp if line
>>   end
>> end
nil
>> sio.next_line 
"line one"
>> sio.next_line 
"line two"
>> sio.next_line 
nil

OK だ。Ruby はプログラマに自由と責任を与えているので、上記のように容易に実現することができる。 ただ、注意点としては、既に存在するメソッドであっても容易に変更可能である点。

>> "Hello".to_s
"Hello"
>> class String
>>   def to_s; "Overrided"; end
>> end
nil
>> "Hello".to_s
"Overrided"
>> "World".to_s
"Overrided"

意図する/しないに関わらず、容易にオーバーライドできてしまうので、既存のクラスを拡張する場合には注意と、オーバーライドした場合には、それなりの対応は必要となる。

Scala は trait を利用

では、Scala の場合。

よく参考にさせて頂いている以下の記事から引用させて頂く。

Ruby の場合と同様に、文字列のストリームから次の行の文字列を取得するメソッドを追加する。

ここでは、既存のクラスの java.io.Reader に対して、nextLine メソッドを追加する。

java.io.Reader は、read メソッドでバイト単位に文字を読み込むことができるのだが、行単位で読み込むメソッドの実装は無い。 これを行単位で行えるようにしよう、という試み。
(java.io.BufferedReader に繋いでしまえばいいのだが、ここは例として。。)

java.io.Reader に追加することで、その恩恵はそのサブクラスでも受けられるので、実装された nextLine メソッドを java.io.StringReader で利用する。

trait を使って実現する。

scala> trait Lines {
     |   this: java.io.Reader =>
     |   def nextLine: Option[String] = {
     |     val builder = new StringBuilder 
     |     var next = read
     | 
     |     while (next != -1 && next.toByte.toChar != '\n') {
     |       builder += next.toByte.toChar
     |       next = read
     |     }
     | 
     |     if (builder.isEmpty) None else Some(builder.toString)
     |   }
     | }
defined trait Lines

上記のように Lines という trait を作成し、これを java.io.StringReader に Mix-In する手法をとる。

scala> val reader = new java.io.StringReader("line one\nline two") with Lines
reader: java.io.StringReader with Lines = $anon$1@42dc5733

scala> reader.nextLine
res0: Option[String] = Some(line one)

scala> reader.nextLine
res1: Option[String] = Some(line two)

scala> reader.nextLine
res2: Option[String] = None

OK だ。

this: java.io.Reader => という表記は、self type (自分型) の定義で、クラス内で this が使われるときに this の型として想定されるものを定義している。 これにより、Lines trait で read というメソッドを使っているが、これは、自分型 self で指定した java.io.Readerread メソッドが使用されている。

Implicit Convension (暗黙の型変換) を使っても実現できるのだろうが、trait を使用した方が今の自分にはわかり易くてよい。。

なお、Scala の場合は、既に実装済みの既存のメソッドをオーバーライドする場合には、明示的に override 修飾子を必要とするため、意図しないオーバーライドはコンパイル時に気付くことができる。

blog comments powered by Disqus