僕はもともとRubyが好きで3年くらい書いており、Scalaはまだ始めて3ヶ月くらいなのですが、今回はそんなRubyistから見たScalaについて書こうかと思います。
Rubyはかなり自由な言語で、あらゆること(メタプログラミングを含む)が簡単にできるようになっているのですが、もしかして頑張ればScalaでもできるのでは!?と思いここ最近調べてみました。
なお、今回の内容は以前Kawasaki.rbで発表した以下のスライドがベースとなっています。
http://www.slideshare.net/yutomatsukubo/rubyistscala
オープンクラス
Rubyにはオープンクラスがあり、既存のクラスを簡単に拡張することができます。
# 既存のStringクラスを拡張
class String
def add_scala
self + "scala"
end
end
puts "ruby is like ".add_scala # => ruby is like scala
Scalaでこれを実現する場合、拡張メソッドを使います。
値クラスと汎用トレイト
http://docs.scala-lang.org/ja/overviews/core/value-classes.html
object MyApp extends App {
implicit class MyString(val s: String) extends AnyVal {
def addScala = s + "Scala"
}
println("Ruby is like ".addScala) // => "Ruby is like Scala"
}
Rubyに比べると大分おまじない要素が増えたように見えますが、なんとこれだけの記述で実現することができてしまいました。
なお、Rubyでは既存のメソッドすらオーバーライドできてしまいますが、
puts "Ruby".size # => 4
class String
def size
-1
end
end
puts "Ruby".size # => -1
さすがにScalaではそこまでサポートはされていませんでした。(できても困るが)
object MyApp extends App {
implicit class MyString(val s: String) extends AnyVal {
override def size: Int = -10
}
println("Scala".size)
// Note that implicit conversions are not applicable because they are ambiguous
// both method augmentString in object Predef of type (x: String)scala.collection.immutable.StringOps
// and method MyString in object MyApp of type (s: String)MyApp.MyString
// are possible conversion functions from String("Scala") to ?{def size: ?}
}
method_missing
Rubyには、存在しないメソッドを呼び出した時の挙動を指定できるmethod_missing
メソッドがあります。
method_missing
http://ref.xaio.jp/ruby/method_missing
#!/usr/bin/env ruby
class MyClass
def foo
"foo"
end
def method_missing(name)
"#{name} is missing!!"
end
end
my = MyClass.new
puts my.foo # => "foo"
puts my.bar # => "bar is missing!!"
Scalaでこれを実現するにはDynamicトレイトが使えます。
Dynamic
http://www.scala-lang.org/api/current/index.html#scala.Dynamic
import scala.language.dynamics
class MyClass extends Dynamic {
def foo = "foo"
def selectDynamic(name: String): String = s"${name} is missing!"
}
object MyApp extends App {
val my = new MyClass
println(my.foo) // => foo
println(my.bar) // => bar is missing!
}
静的言語でこれが可能というのは正直驚きでした。
ダックタイピング
Rubyは動的型付け言語なので、型が適切かの判断はメソッドが呼べるかどうか(ダックタイピング)で決まります。
ScalaではStructural Subtypingを使えばこれが実現できます。
// hogeメソッドを持つクラス
class MyClass {
def hoge: String = "hoge!"
}
// hogeメソッドを持たないクラス
class MyClass2 {
def piyo: String = "piyo!"
}
object MyApp extends App {
// hogeメソッドを持つ型を定義
type Hoge = {
def hoge: String
}
// Hogeタイプを受け取るメソッド
def addFuga(t: Hoge): String = {
t.hoge + "fuga!"
}
val my = new MyClass
println(addFuga(my)) // => "hoge!fuga!"
val my2 = new MyClass2
println(addFuga(my2)) // => コンパイルエラー: type mismatch;
}
かなり頑張る形になりましたが、MyClassはhoge
メソッドを持つだけでコンパイルが通り、MyClass2はコンパイルが通りません。
どういった用途でこの機能を使うのかは今のところ僕にはわかっていません。
おわりに
Scalaは静的型付けと関数型プログラミング言語のイメージがあり、硬い言語なのだろうと思っていたのですが、意外に動的で自由な一面を持つ面白い言語だということに気づけました。
Rubyistとしては、今後は是非メタプログラミングの発展も願っています!