Upgrade to Pro — share decks privately, control downloads, hide ads and more …

String Interpolation in Scala 2.10

String Interpolation in Scala 2.10

Talk at ScalaSyd February 2013

Tony Sloane

August 21, 2014
Tweet

More Decks by Tony Sloane

Other Decks in Programming

Transcript

  1. 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
  2. Overview Scala strings: before and after Implementing your own string

    interpolators Pattern matching Syntax checking Concrete object syntax
  3. 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)
  4. 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 !!!
  5. 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"
  6. 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
  7. (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
  8. (Digression) New in 2.10: implicit classes implicit class MyInt (i

    : Int) { def sayhello = s"Hello there: $i" } println (42. sayhello) Hello there: 42
  9. 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
  10. 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)
  11. 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") } } }
  12. 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)
  13. 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 }
  14. 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
  15. 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) }
  16. 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") ^
  17. 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
  18. (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
  19. 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) } } }
  20. 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
  21. 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
  22. 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))))
  23. 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))))
  24. 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") ^
  25. 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)
  26. 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
  27. 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
  28. 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)))
  29. 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
  30. 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