• Like
Simple Scala DSLs
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

Simple Scala DSLs

  • 1,800 views
Published

Presentation at NY Scala Enthusiasts Meetup on 6/14/2010. Covers techniques for using Scala's flexible syntax and features to design internal DSLs and wrappers.

Presentation at NY Scala Enthusiasts Meetup on 6/14/2010. Covers techniques for using Scala's flexible syntax and features to design internal DSLs and wrappers.

Published in Technology , Business
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
1,800
On SlideShare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
61
Comments
1
Likes
4

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide




































































Transcript

  • 1. Simple Scala DSLs May 21, 2010 1
  • 2. A Definition 2
  • 3. A Definition A DSL is a custom way to represent logic designed to solve a specific problem. 2
  • 4. A Definition A DSL is a custom way to represent logic designed to solve a specific problem. I’m going to try and give you tools to stretch Scala’s syntax to match the way you think about the logic of your specific problem. 2
  • 5. Key Scala features • Syntactic sugar • Implicit methods • Options • Higher order functions 3
  • 6. Syntactic Sugar You can omit . and () for any method which takes a single parameter. map get “key” == map.get(“key”) 4
  • 7. Syntactic Sugar Methods whose names end in : bind to the right. “key” other: obj == obj.other:(“value”) val newList = item :: oldList val newList = oldList.::(item) 5
  • 8. Syntactic Sugar the apply() method a(b) == a.apply(b) map(“key”) == map.apply(“key”) 6
  • 9. Syntactic Sugar the update() method a(b) = c == a.update(b, c) a(b, c) = d == a.update(b, c, d) map(“key”) = “value” == map.update(“key”, “value”) 7
  • 10. Syntactic Sugar setters and getters object X { var y = 0 } object X { private var _z: Int = 0 def y = _z def y_=(i: Int) = _z = i } X.y => 0 X.y = 1 => Unit X.y => 1 8
  • 11. Syntactic Sugar tuples (a, b) == Tuple2[A,B](a, b) (a, b, c) == Tuple3[A,B,C](a, b, c) val (a, b) = sometuple // extracts 9
  • 12. Syntactic Sugar unapply() - used to extract values in pattern matching object Square { def unapply(p: Pair[Int, Int]) = p match { case (x, y) if x == y => Some(x) case _ => None } } (2, 2) match { case Square(side) => side*side case _ => -1 } 10
  • 13. Syntactic Sugar varargs - sugar for a variable length array def join(arr: String*) = arr.mkString(“,”) join(“a”) => “a” join(“a”, “b”) => “a,b” def join(arr: Array[String]) = arr.mkString(“,”) join(Array(“a”, “b”)) => “a,b” 11
  • 14. Syntactic Sugar unapplySeq() - an extractor that supports vararg matching object Join { def apply(l: String*) = l.mkString(“,”) def unapplySeq(s: String) = Some(s.split(“,”)) } Join(“1”, “2”, “3”) match { case Join(“1”, _*) => println(“starts w/1”) case _ => println(“doesn’t start w/1”) } 12
  • 15. Syntactic Sugar 13
  • 16. Syntactic Sugar Case Classes are regular classes which export their constructor parameters and which provide a recursive decomposition mechanism via pattern matching. 13
  • 17. Syntactic Sugar Case Classes are regular classes which export their constructor parameters and which provide a recursive decomposition mechanism via pattern matching. case class Square(side: Int) 13
  • 18. Syntactic Sugar Case Classes are regular classes which export their constructor parameters and which provide a recursive decomposition mechanism via pattern matching. case class Square(side: Int) val s = Square(2) // apply() is defined 13
  • 19. Syntactic Sugar Case Classes are regular classes which export their constructor parameters and which provide a recursive decomposition mechanism via pattern matching. case class Square(side: Int) val s = Square(2) // apply() is defined s.side => 2 // member is exported 13
  • 20. Syntactic Sugar Case Classes are regular classes which export their constructor parameters and which provide a recursive decomposition mechanism via pattern matching. case class Square(side: Int) val s = Square(2) // apply() is defined s.side => 2 // member is exported val Square(x) = s // unapply() is defined 13
  • 21. Syntactic Sugar Case Classes are regular classes which export their constructor parameters and which provide a recursive decomposition mechanism via pattern matching. case class Square(side: Int) val s = Square(2) // apply() is defined s.side => 2 // member is exported val Square(x) = s // unapply() is defined s.toString ==> “Square(2)” 13
  • 22. Syntactic Sugar Case Classes are regular classes which export their constructor parameters and which provide a recursive decomposition mechanism via pattern matching. case class Square(side: Int) val s = Square(2) // apply() is defined s.side => 2 // member is exported val Square(x) = s // unapply() is defined s.toString ==> “Square(2)” s.hashCode ==> 630363263 13
  • 23. Syntactic Sugar Case Classes are regular classes which export their constructor parameters and which provide a recursive decomposition mechanism via pattern matching. case class Square(side: Int) val s = Square(2) // apply() is defined s.side => 2 // member is exported val Square(x) = s // unapply() is defined s.toString ==> “Square(2)” s.hashCode ==> 630363263 s == Square(2) ==> true 13
  • 24. Syntactic Sugar 14
  • 25. Syntactic Sugar Many classes can be used in for comprehensions because they implement some combination of: map[B](f: (A) => B): Option[B] flatMap[B](f: (A) => Option[B]): Option[B] filter(p: (A) => Boolean): Option[A] foreach(f: (A) => Unit): Unit 14
  • 26. Syntactic Sugar Many classes can be used in for comprehensions because they implement some combination of: map[B](f: (A) => B): Option[B] flatMap[B](f: (A) => Option[B]): Option[B] filter(p: (A) => Boolean): Option[A] foreach(f: (A) => Unit): Unit for { i <- List(1,2,3) val x = i * 3 if x % 2 == 0 } yield x ==> List(6) List(1,2,3).map(_ * 3).filter(_ % 2 == 0) ==> List(6) 14
  • 27. Implicit Methods Implicit methods are a compromise between the closed approach to extension of Java and the open approach to extension in Ruby. Implicit Methods are: 15
  • 28. Implicit Methods Implicit methods are a compromise between the closed approach to extension of Java and the open approach to extension in Ruby. Implicit Methods are: • lexically scoped / non-global 15
  • 29. Implicit Methods Implicit methods are a compromise between the closed approach to extension of Java and the open approach to extension in Ruby. Implicit Methods are: • lexically scoped / non-global • statically typed 15
  • 30. Implicit Methods Implicit methods are inserted by the compiler under the following rules: 16
  • 31. Implicit Methods Implicit methods are inserted by the compiler under the following rules: • they are in scope 16
  • 32. Implicit Methods Implicit methods are inserted by the compiler under the following rules: • they are in scope • the selection is unambiguous 16
  • 33. Implicit Methods Implicit methods are inserted by the compiler under the following rules: • they are in scope • the selection is unambiguous • it is not already in an implicit i.e. no nesting 16
  • 34. Implicit Methods Implicit methods are inserted by the compiler under the following rules: • they are in scope • the selection is unambiguous • it is not already in an implicit i.e. no nesting • the code does not compile as written 16
  • 35. Implicit Methods: Usage Extending Abstractions: Java class IntWrapper(int i) { int timesTen() { return i * 10; } } int i = 2; new IntWrapper(i).timesTen(); 17
  • 36. Implicit Methods: Usage Extending Abstractions: Java Java’s abstractions are totally sealed and we must use explicit wrapping each time we wish to extend them. Pros: safe Cons: repetitive boilerplate at each call site 18
  • 37. Implicit Methods: Usage Extending Abstractions: Ruby class Int def timesTen self * 10 end end i = 2 i.timesTen 19
  • 38. Implicit Methods: Usage Extending Abstractions: Ruby Ruby’s abstractions are totally open. We can modify the behavior of existing classes and objects in pretty much any way we want. Pros: declarative, powerful and flexible Cons: easily abused and can be extremely difficult to debug 20
  • 39. Implicit Methods: Usage Extending Abstractions: Scala class IntWrapper(i: Int) { def timesTen = i * 10 } implicit def wrapint(i: Int) = new IntWrapper(i) val i = 2 i.timesTen 21
  • 40. Implicit Methods: Usage Extending Abstractions: Scala Scala’s approach is both powerful and safe. While it is certainly possible to abuse, it naturally encourages a safer approach through lexical scoping. Pros: more powerful than java, safer than ruby Cons: can result in unexpected behavior if not tightly scoped 22
  • 41. Implicit Methods: Usage Normalizing parameters def remainder[T <: Double](num: T): Double = num - num.floor 23
  • 42. Implicit Methods: Usage Normalizing parameters def remainder[T <: Double](num: T): Double = num - num.floor remainder(.4) ==> 0 23
  • 43. Implicit Methods: Usage Normalizing parameters def remainder[T <: Double](num: T): Double = num - num.floor remainder(.4) ==> 0 remainder(1.2) ==> 1.2 23
  • 44. Implicit Methods: Usage Normalizing parameters def remainder[T <: Double](num: T): Double = num - num.floor remainder(.4) ==> 0 remainder(1.2) ==> 1.2 remainder(120d) ==> 0 23
  • 45. Implicit Methods: Usage Normalizing parameters 24
  • 46. Implicit Methods: Usage Normalizing parameters remainder(120) ==> error: inferred type arguments [Int] do not conform to method remainder's type parameter bounds [T <: Double] 24
  • 47. Implicit Methods: Usage Normalizing parameters remainder(120) ==> error: inferred type arguments [Int] do not conform to method remainder's type parameter bounds [T <: Double] implicit def i2d(i: Int): Double = i.toDouble 24
  • 48. Implicit Methods: Usage Normalizing parameters remainder(120) ==> error: inferred type arguments [Int] do not conform to method remainder's type parameter bounds [T <: Double] implicit def i2d(i: Int): Double = i.toDouble remainder(120) ==> 0 24
  • 49. Options Options are scala’s answer to null. Options are a very simple, 3-part class hierarchy: 25
  • 50. Options Options are scala’s answer to null. Options are a very simple, 3-part class hierarchy: • sealed abstract class Option[+A] extends Product 25
  • 51. Options Options are scala’s answer to null. Options are a very simple, 3-part class hierarchy: • sealed abstract class Option[+A] extends Product • case final class Some[+A](val x : A) extends Option[A] 25
  • 52. Options Options are scala’s answer to null. Options are a very simple, 3-part class hierarchy: • sealed abstract class Option[+A] extends Product • case final class Some[+A](val x : A) extends Option[A] • case object None extends Option[Nothing] 25
  • 53. Options: Common Methods def get: A Some(1).get ==> 1 None.get ==> java.util.NoSuchElementException! 26
  • 54. Options: Common Methods def getOrElse[B >: A](default: => B): B Some(1).getOrElse(2) ==> 1 None.getOrElse(2) ==> 2 27
  • 55. Options: Common Methods def map[B](f: (A) => B): Option[B] Some(1).map(_.toString) ==> Some(“1”) None.map(_.toString) ==> None 28
  • 56. Options: Usage Replacing Null Checks A (contrived) Java example: int result = -1; int x = calcX(); if (x != null) { int y = calcY(); if (y != null) { result = x * y; } } 29
  • 57. Options: Usage Replacing Null Checks def calcX: Option[Int] def calcY: Option[Int] for { val x <- Some(3) val y <- Some(2) } yield x * y ==> Some(6) 30
  • 58. Options: Usage Replacing Null Checks for { val x <- None val y <- Some(2) } yield x * y ==> None 31
  • 59. Options: Usage Replacing Null Checks (for { val x <- None val y <- Some(2) } yield x * y).getOrElse(-1) ==> -1 32
  • 60. Options: Usage Replacing Null Checks (for { val x <- Some(3) val y <- Some(2) } yield x * y).getOrElse(-1) ==> 6 33
  • 61. Options: Usage Safely Transforming Values val x: Option[Int] = calcOptionalX() def xform(i: Int): Int 34
  • 62. Options: Usage Safely Transforming Values val x: Option[Int] = calcOptionalX() def xform(i: Int): Int (x map xform) getOrElse -1 34
  • 63. Options: Usage Safely Transforming Values val x: Option[Int] = calcOptionalX() def xform(i: Int): Int (x map xform) getOrElse -1 (for (i <- x) yield xform(i)).getOrElse(-1) 34
  • 64. Implicits + Options 35
  • 65. Implicits + Options val m = Map(“1” -> 1,“map” -> Map(“2” -> 2)) 35
  • 66. Implicits + Options val m = Map(“1” -> 1,“map” -> Map(“2” -> 2)) We’d like to be able to access the map like this: m/”key1”/”key2”/”key3” 35
  • 67. Implicits + Options val m = Map(“1” -> 1,“map” -> Map(“2” -> 2)) We’d like to be able to access the map like this: m/”key1”/”key2”/”key3” But, we’d like to not have to constantly check nulls or Options. 35
  • 68. Implicits + Options class MapWrapper(map: Map[String, Any]) { def /(s: String): Option[Any] = map.get(s) } implicit def a2mw(a: Any) = new MapWrapper(a.asInstanceOf[Map[String, Any]]) 36
  • 69. Implicits + Options class MapWrapper(map: Map[String, Any]) { def /(s: String): Option[Any] = map.get(s) } implicit def a2mw(a: Any) = new MapWrapper(a.asInstanceOf[Map[String, Any]]) m/”a” ==> None 36
  • 70. Implicits + Options class MapWrapper(map: Map[String, Any]) { def /(s: String): Option[Any] = map.get(s) } implicit def a2mw(a: Any) = new MapWrapper(a.asInstanceOf[Map[String, Any]]) m/”a” ==> None m/”1” ==> Some(1) 36
  • 71. Implicits + Options class MapWrapper(map: Map[String, Any]) { def /(s: String): Option[Any] = map.get(s) } implicit def a2mw(a: Any) = new MapWrapper(a.asInstanceOf[Map[String, Any]]) m/”a” ==> None m/”1” ==> Some(1) m/”map”/”2” ==> ClassCastException! 36
  • 72. Implicits + Options class OptionMapWrapper( o: Option[MapWrapper]) { def /(s: String) = o match { case Some(mw) => mw/s case _ => None } } implicit def o2omw(o: Option[Any]) = new OptionMapWrapper(o map a2mw) 37
  • 73. Implicits + Options class OptionMapWrapper( o: Option[MapWrapper]) { def /(s: String) = o match { case Some(mw) => mw/s case _ => None } } implicit def o2omw(o: Option[Any]) = new OptionMapWrapper(o map a2mw) m/”map”/”2” ==> Some(2) 37
  • 74. Implicits + Options class OptionMapWrapper( o: Option[MapWrapper]) { def /(s: String) = o match { case Some(mw) => mw/s case _ => None } } implicit def o2omw(o: Option[Any]) = new OptionMapWrapper(o map a2mw) m/”map”/”2” ==> Some(2) m/”map”/”next”/”blah” ==> None 37
  • 75. Higher Order Functions Higher order functions are functions that: • take functions as parameters AND / OR • result in a function 38
  • 76. Higher Order Functions 39
  • 77. Higher Order Functions Let’s say we’d like to write a little system for easily running statements asynchronously via either threads or actors. 39
  • 78. Higher Order Functions Let’s say we’d like to write a little system for easily running statements asynchronously via either threads or actors. What we’d like to get to: run (println(“hello”)) using threads run (println(“hello”)) using actors 39
  • 79. Higher Order Functions trait RunCtx { def run(f: => Unit): Unit } class Runner(f: => Unit) { def using(ctx: RunCtx) = ctx run f } def run(f: => Unit) = new Runner(f) 40
  • 80. Higher Order Functions object actors extends RunCtx { def run(f: => Unit) = scala.actors.Actor.actor(f) } object threads extends RunCtx { def run(f: => Unit) = { object t extends Thread { override def run = f } t.start } } 41
  • 81. Thank You! email: lincoln@hotpotato.com twitter: 11nc hotpotato: lincoln Questions? 42