Scala 2.10.0で追加されたString Interpolationですが、これまでs補間子やf補間子のような既存のものは使ってきましたが、そういえば自分で書いたことがなかったので試してみました。
参考にしたのは、以下の2つのサイトです。
文字列の補間
http://docs.scala-lang.org/ja/overviews/core/string-interpolation.html
文字列の補間
http://www.ne.jp/asahi/hishidama/home/tech/scala/string.html
String Interpolationを自分で定義する際には、Implicit ClassかつValue Classにすべきだと。なので、まずはこんなクラスを用意。
src/main/scala/MyStringInterpolation.scala
object MyStringInterpolation { implicit class SimpleStringInterpolation(val sc: StringContext) extends AnyVal { // ここに定義を書く } }
StringContextに対する、Implicit Conversionを定義するわけですね。
では最初は、とりあえず定数値を返すようなString Interpolationを書いてみます。
implicit class SimpleStringInterpolation(val sc: StringContext) extends AnyVal { def helloworld(args: Any*): String = "Hello World" }
先のImplicit Classのメソッドとして定義すればよいみたいです。
このメソッド名が、そのままString Interpolationとして使えます。
*確認コードは、ScalaTestを使っています
describe("helloworld") { it("foobar") { helloworld"foobar" should be ("Hello World") } it("foobar${i}foobar") { val i = 10 helloworld"foobar${i}foobar" should be ("Hello World") } }
何を入力されても、「Hello World」が返ります。もちろん、何の役にも立ちませんが。
ちなみに、この時にString Interpolationの定義をimportするのをお忘れなく。
import MyStringInterpolation._
続いて、今度は引数の内容を解析してみます。Stringを返すのではなく、リテラルの部分と変数の部分をそれぞれListにして、かつタプルにして返却してみます。String Interpolationの評価結果として、必ずしもStringを返却する必要はありません。
def tuples(args: Any*): (List[String], List[Any]) = (sc.parts.toList, args.toList)
StringContext#partsでリテラル部が、変数部分は定義したString Interpolationの引数として渡されます。
よって、このtuples補間子を使うと、こういうことになります。
describe("tuples") { it("Hello${v1}World${v2}!!") { val (v1, v2) = ("str1", "str2") tuples"Hello${v1}World${v2}!!" should be (List("Hello", "World", "!!"), List("str1", "str2")) } it("Hello${1 + 2}World") { tuples"Hello${1 + 2}World" should be (List("Hello", "World"), List(3)) } }
StringContext#partsと、メソッド引数の対比がわかりましたね。
ところで、通常のs補間子と同じような動作をさせようとするString Interpolationを作成してみようとすると
def noesc(args: Any*): String = { val ai = args.iterator sc.parts.reduceLeft[String] { case (acc, p) => acc + ai.next + p } }
なんと、エスケープシーケンスがかかった文字の解釈が妙なことになります。
describe("noesc") { it("\\t\\n$s\\t\\n") { val s = "str" noesc"\\t\\n$s\\t\\n" should be ("\\\\t\\\\nstr\\\\t\\\\n") } }
「\」が倍になりましたね。
そこで、今度はStringContext#treatEscapesを、StringContext#partsの各Stringにかけてみます。
def esc(args: Any*): String = { val ai = args.iterator sc.parts.tail.foldLeft(StringContext.treatEscapes(sc.parts.head)) { case (acc, p) => acc + ai.next + StringContext.treatEscapes(p) } }
今度は、エスケープシーケンスが入ったものも、正しく扱われます。
describe("esc") { it("\\t\\n$s\\t\\n") { val s = "str" esc"\\t\\n$s\\t\\n" should be ("\\t\\nstr\\t\\n") } }
こうすると、s補間子と同じ動作になるわけですね。
describe("esc") { it("esc == s") { val str = "foo" val i = 10 esc"Hello $str \\t \\n World $i" should be (s"Hello $str \\t \\n World $i") } }
ちなみに、自分で定義したString Interpolationでも、存在しない変数とかを埋め込んだりすると
it("\\t\\n$s\\t\\n") { // コメントアウト! // val s = "str" esc"\\t\\n$s\\t\\n" should be ("\\t\\nstr\\t\\n") }
コンパイルエラーになります。素晴らしい。
src/test/scala/MyStringInterpolationTest.scala:42: not found: value s [error] esc"\\t\\n$s\\t\\n" should be ("\\t\\nstr\\t\\n") [error] ^ [error] one error found
というわけで、String Interpolationを定義する時は、やっぱり定義済みのString Interpolation、つまりStringContextのソースを見てみる感じでしょうね。
試したコードは、こちらにあります。
https://github.com/kazuhira-r/string-interpolation-example