Scalaでmethod_missing的なことができる"applyDynamic"を使って"Term::ANSIColor"みたいなの作った
Scala2.9から、DynamicってのでRubyのmethod_missing的なことができるようになる.....ハズだったんだが、
2.9正式リリースの数時間前にいきなりこの機能はサポートされなくなった。
といっても全く使えなくなったわけではなく、scalacやscalaコマンドに-Xexperimentalオプションを使えば使えるようになる。
で、Dynamicってどんなのかというと、Dynamicというトレイトを実装したクラスは、定義されていないメソッドが呼び出されるとapplyDynamic(methodName:String)(args:Any*)というメソッドが代わりに呼び出されるようになる。
scala> class Foo extends Dynamic { | def applyDynamic(methodName:String)(args:Any*) = { | println( "methodName = " + methodName ) | println( "args = " + args) | } | } defined class Foo scala> val foo = new Foo foo: Foo = Foo@4c0a7e2e scala> foo.bar dynatype: $line7.$read.$iw.$iw.$iw.$iw.foo.applyDynamic("bar")() methodName = bar args = List()
で、こいつを使って、Rubyの"Term::ANSIColor"みたいなヤツを書いてみた。
/** * This library can be used to color/uncolor strings using ANSI escape sequences. * * To implementation, referenced Ruby's "Term::ANSIColor"(http://term-ansicolor.rubyforge.org) * * NOTE * this version's implementation is using Dynamic trait and applyDynamic. * "applyDynamic" is not supported yet. * to enable this feature, you should specify -Xexperimental option when running scala/scalac command. * * Usage: * val coloredStr = "This text would be colored RED!!".colored.red + " and this part would be BLUE!!".colored.blue * println(coloredStr) * */ object Colored { implicit def stringToColered(s:String) = Colored(s) implicit def ColoredToString(c:Colored) = c.toString val attributes = Map( 'clear -> 0, 'reset -> 0, 'bold -> 1, 'dark -> 2, 'italic -> 3, 'underline -> 4, 'underscore -> 4, 'blink -> 5, 'rapid_blink -> 6, 'negative -> 7, 'concealed -> 8, 'strikethrough -> 9, 'black -> 30, 'red -> 31, 'green -> 32, 'yellow -> 33, 'blue -> 34, 'magenta -> 35, 'cyan -> 36, 'white -> 37, 'onBlack -> 40, 'onRed -> 41, 'onGreen -> 42, 'onYellow -> 43, 'onBlue -> 44, 'onMagenta -> 45, 'onCyan -> 46, 'onWhite -> 47 ).map{case (k, v) => (k, "\033[%dm" format v) } private val Reset = escape('reset) getOrElse(0) private def escape(attr:Symbol):Option[String] = attributes.get(attr) private def escape(attr:String):Option[String] = escape(Symbol(attr)) private def escaped(attr:Symbol)(s: => String):Colored = Colored( escape(attr) + s ) private def escaped(attr:String)(s: => String):Colored = escaped(Symbol(attr))(s) def apply(s:String) = new Colored{ val str = s } val colored = Colored("") } trait Colored extends Dynamic { val str:String def +(other:String):Colored = Colored( this.toString + other) def +(other:Colored):Colored = Colored( this.toString + other.toString ) def colored = this def applyDynamic(attr: String)(args: Any*):Colored = coloring(attr) + args.headOption.getOrElse("") private def coloring(attr:String) = Colored.escape(attr) map{ esc => Colored(esc + str) } getOrElse(this) override def toString = str + Colored.Reset } object Main extends App { import Colored._ println( "This text would be colored RED!!".colored.red + " and this part would be BLUE!!".colored.blue ) println( "白字に赤背景であばばばばばばばば".colored.onWhiltecolored.onRed.white) }
F.Y.I