Slide 1

Slide 1 text

String Interpolation in Scala 2.10 Anthony M. Sloane Programming Languages Research Group Department of Computing Macquarie University [email protected] [email protected] @inkytonik February 13, 2013

Slide 2

Slide 2 text

Overview Scala strings: before and after Implementing your own string interpolators Pattern matching Syntax checking Concrete object syntax

Slide 3

Slide 3 text

Scala strings string literals "A string on one line" """A long string with only Unicode escapes and possibly newlines in it""" constructing strings "The " + animal1 + " jumped over the " + animal2 "The %s jumped over the %s".format (animal1 , animal2)

Slide 4

Slide 4 text

New in 2.10: inline expression insertion (s, raw) val answer = 42 println (s"answer is $answer , dollar is $$") val animal1 = "fox" val animal2 = "dog" println (s"The $animal1 jumped over the $animal2") println (s"One plus one is ${1 + 1}") println (s"The inserted expressions are blocks ${ val x = "!" x * 3 }") answer is 42, dollar is $ The fox jumped over the dog One plus one is 2 The inserted expressions are blocks !!!

Slide 5

Slide 5 text

New in 2.10: formatted insertion (f) val pi = 3.14159 println (f"pi ($pi) = $pi %1.3f") val msg = "G’day!" println (f"msg.length = ${msg.length }%5d") pi (3.14159) = 3.142 msg.length = 6 expressions are type-checked against their format specifier f"msg can’t be formatted as $msg %1.3f"

Slide 6

Slide 6 text

How does it work? syntactic sugar id"text0${expr1}text1 ... ${exprn}textn" becomes StringContext ("text0", "text1", ... , "textn").id ( expr1 , ... , exprn) e.g., the StringContext.s method returns esc ("text0") + (expr1) + esc ("text1") ... + (exprn) + esc ("textn") where esc performs escape sequence processing

Slide 7

Slide 7 text

(Digression) Implicit conversions class MyInt (val i : Int) { def sayhello = s"Hello there: $i" } implicit def ImplicitConversion (i : Int) : MyInt = new MyInt (i) println (42. sayhello) Hello there: 42

Slide 8

Slide 8 text

(Digression) New in 2.10: implicit classes implicit class MyInt (i : Int) { def sayhello = s"Hello there: $i" } println (42. sayhello) Hello there: 42

Slide 9

Slide 9 text

Writing our own interpolator implicit class ReverseC (val sc : StringContext ) { def rev (args : Any*) : String = { val orig = sc.s (args : _*) orig.reverse } } val msg = "Hello world!" println (rev"Backwards version of $msg") !dlrow olleH fo noisrev sdrawkcaB

Slide 10

Slide 10 text

Constructing values that are not strings case class Count (num : Int) implicit class SpaceCountC (val sc : StringContext ) { def nspaces (args : Any*) : Count = { val orig = sc.s (args : _*) Count (orig.count (_.isSpaceChar)) } } val msg1 = "Hello world!" val msg2 = s"a b $msg1 c d" println (nspaces"$msg1") println (nspaces"$msg2") Count (1) Count (5)

Slide 11

Slide 11 text

Octal number literals implicit class OctalC (val sc : StringContext ) { def o () : Int = { val orig = sc.s () val OctalNum = "[0 -7]+".r orig match { case OctalNum () => Integer.parseInt (orig , 8) case _ => sys.error ("Can only contain 0-7 characters") } } }

Slide 12

Slide 12 text

Run-time syntax checking println (o"177") println (o"49") 127 java.lang. RuntimeException : Can only contain 0-7 characters at scala.sys.package$.error(package.scala :27) at Octal$OctalC .o(Octal.scala :11) at Octal$.main(Octal.scala :18)

Slide 13

Slide 13 text

Compile-time checking using a macro import scala.language.experimental .macros import scala.reflect.macros.Context implicit class OctalC (val sc : StringContext ) { def o () : Int = macro OctalImpl.oImpl }

Slide 14

Slide 14 text

Macro implementation: access the prefix tree OctalMacros.OctalC ( scala. StringContext.apply ( "177")).o () def oImpl (c : Context) () : c.Expr[Int] = { import c.{ universe => u} import u._ val Apply (_, List ( Apply (_, List ( Literal (Constant (orig : String)))))) = c.prefix.tree

Slide 15

Slide 15 text

Macro implementation: check, construct or complain val OctalNum = "[0 -7]+".r orig match { case OctalNum () => c.Expr[Int] ( Literal ( Constant ( Integer.parseInt (orig , 8)))) case _ => c.error (c.enclosingPosition , "Must only contain 0-7 characters") reify (0) }

Slide 16

Slide 16 text

Use the macro-based interpolator object OctalWithMacro { import OctalMacros.OctalC def main (args : Array[String ]) { println (o"177") } } 127 OctalWithMacro .scala :8: error: Must only contain 0-7 characters println (o"49") ^

Slide 17

Slide 17 text

The other side of the coin: pattern matching id"text0${pat1}text1 ... ${patn}textn" becomes StringContext ("text0", "text1", ... , "textn").id ( pat1 , ... , patn) id is an extractor object Combine construction and pattern matching together in a single object with both apply and unapply/unapplySeq methods

Slide 18

Slide 18 text

(Digression) Extractor objects case class Foo (i : Int , s : String) e match { case Foo (p1 , p2) => ... } A call Foo.unapply (e) is made which either returns None if e is not matched, or Some ((v1, v2)) if it matches with two pieces v1 and v2. v1 and v2 are recursively matched against p1 and p2

Slide 19

Slide 19 text

A version of s that also pattern matches implicit class MySC (val sc : StringContext ) { object mys { def apply (args : Any*) : String = sc.s (args : _*) def unapplySeq (s : String) : Option[Seq[String ]] = { val regexp = sc.parts.mkString ("(.+)").r regexp.unapplySeq (s) } } }

Slide 20

Slide 20 text

Using interpolated pattern matching println (mys"One plus one is ${1 + 1}") val msg = "The sky is blue" match { case mys"The $thing is $colour" => mys"A $colour thing is $thing" case _ => "no match" } println (msg) One plus one is 2 A blue thing is sky

Slide 21

Slide 21 text

Concrete object syntax Extend these ideas to more complex languages Use a proper parser to ensure that the interpolation is legal Use a macro to get compile-time checking Convenient for writing programs that manipulate other programs: compilers optimisers static analysis tools staged computation

Slide 22

Slide 22 text

Concrete object syntax in construction

Slide 23

Slide 23 text

Expression construction examples println (exp"a + 1") val v : Expression = IdnExp (IdnUse ("a")) println (exp"$v + $v") val w = exp"$v + 1" val x = StarExp (w, IntExp (2)) println (exp"${x.left} + 2 * $v") PlusExp(IdnExp(IdnUse(a)),IntExp (1)) PlusExp(IdnExp(IdnUse(a)),IdnExp(IdnUse(a))) PlusExp(PlusExp(IdnExp(IdnUse(a)),IntExp (1)),StarExp( IntExp (2),IdnExp(IdnUse(a))))

Slide 24

Slide 24 text

Statement construction examples println (stm"a = a + 1;") val o : Expression = exp"1"; val v : Expression = exp"a"; val w : Statement = stm"a = $v + $o;" println (w) println (stm"while ($o) $w") VarAssign(IdnUse(a),PlusExp(IdnExp(IdnUse(a)),IntExp (1))) VarAssign(IdnUse(a),PlusExp(IdnExp(IdnUse(a)),IntExp (1))) While(IntExp (1),VarAssign(IdnUse(a),PlusExp(IdnExp( IdnUse(a)),IntExp (1))))

Slide 25

Slide 25 text

Syntax error examples val v : Expression = exp"a" MiniJavaWrong .scala :8: error: expression expected println (exp"2 + ") ^ MiniJavaWrong .scala :9: error: statement expected println (stm"a := $v + 1") ^ MiniJavaWrong .scala :10: error: statement expected println (stm"while (1) $v") ^

Slide 26

Slide 26 text

Expression pattern matching examples val e = exp"1 + 2" e match { case exp"1 + 2" => println ("match") case _ => println ("no match") } e match { case exp"$p" => println (s"p=$p") } e match { case exp"$l + $r" => println (s"l=$l r=$r") case _ => println ("no match") } match p=PlusExp(IntExp (1),IntExp (2)) l=IntExp (1) r=IntExp (2)

Slide 27

Slide 27 text

Nested expression pattern matching (1) val e = exp"1 * 2 + a" e match { case exp"$l + $r" => println (s"l=$l r=$r") case _ => println ("no match") } e match { case exp"$ll * $lr + $r" => println (s"ll=$ll lr=$lr r=$r") case _ => println ("no match") } e match { case exp"$l * $r" => println (s"l=$l r=$r") case _ => println ("no match") } l=StarExp(IntExp (1),IntExp (2)) r=IdnExp(IdnUse(a)) ll=IntExp (1) lr=IntExp (2) r=IdnExp(IdnUse(a)) no match

Slide 28

Slide 28 text

Nested expression pattern matching (2) def trymatch (x : Expression) { x match { case exp"${IntExp (l)} + ${IntExp (r)}" => println (s"l=$l r=$r") case _ => println ("no match") } } trymatch (exp"1 + 2") trymatch (exp"a + 2") l=1 r=2 no match

Slide 29

Slide 29 text

Statement pattern matching examples val s = stm"a = a + 1;" s match { case stm"a = $e;" => println (s"e=$e") case _ => println ("no match") } s match { case stm"a = $e + 1;" => println (s"e=$e") case _ => println ("no match") } val t = stm"while (1) a = a + 1;" t match { case stm"while ($e) $s" => println (s"e=$e s=$s") case _ => println ("no match") } e=PlusExp(IdnExp(IdnUse(a)),IntExp (1)) e=IdnExp(IdnUse(a)) e=IntExp (1) s=VarAssign(IdnUse(a),PlusExp(IdnExp( IdnUse(a)),IntExp (1)))

Slide 30

Slide 30 text

Concrete object syntax is a work in progress Still to be packaged into an easily reusable unit Macro code yet to be fine-tuned More type signatures are needed than we would like Pattern matching relies on a feature of the nightly Scala distribution

Slide 31

Slide 31 text

Further reading at http://docs.scala-lang.org String interpolation Overview: overviews/core/string-interpolation.html SIP 11: sips/pending/string-interpolation.html Reflection: overviews/reflection/overview.html Macros: overviews/macros/overview.html