Lecture notes for COMP 371/471: Programming languages course at Loyola University Chicago with a focus on functional languages and projects mostly in Scala
@channel Wrapping up the example from class...
sealed trait Shape
case class Rectangle(width: Int, height: Int) extends Shape
case class Location(x: Int, y: Int, shape: Shape) extends Shape {
require(shape != null, "null shape in location")
}
case class Group(shapes: Shape*) extends Shape {
require(shapes != null, "null shape in location") // TODO is this sufficient?
}
val r = Rectangle(20, 40)
val q = Rectangle(20, 40)
val p = Rectangle(20, 30)
val g = Group(r, Group(q, p), Location(10, 15, r))
def countGroup(s: Shape): Int = s match {
case Rectangle(w, h) => 0
case Location(x, y, c) => ???
case Group(shapes @ _*) => ???
}
countGroup(r)
@channel As I mentioned, the ??? is a placeholder for "not yet implemented".
So countGroup(r) returns 0 as expected.
But it would raise a NYI exception for group or location nodes.
Now we need to apply recursive thinking:
For location, the child might have group nodes.
For group, the current node is a group node, plus the children might have group nodes.
Hence...
def countGroup(s: Shape): Int = s match {
case Rectangle(w, h) => 0
case Location(x, y, c) => countGroup(c)
case Group(shapes @ *) =>
var sum = 1
for (c <- shapes)
sum += countGroup(c)
sum
}
Now countGroup(g) returns 2 as expected.
This is a Java-style, imperative implementation.
@channel Or equivalently using the foreach method instead of the so-called for comprehension:
case Group(shapes @ ) =>
var sum = 1
shapes.foreach { c =>
sum += countGroup(c)
}
sum
@channel Now...drum roll...the opportunity to make it functional/applicative/immutable.
case Group(shapes @ _) =>
1 + shapes.map { c => countGroup(c) } .sum
where map transforms each item in a collection with the result of applying the given function to the item
(which design pattern is that?)
and sum adds all the items in a collection.
@channel How would you compare these three implementations in terms of whatever criteria you can think of?
@channel Wrapping up the example from class... sealed trait Shape
case class Rectangle(width: Int, height: Int) extends Shape
case class Location(x: Int, y: Int, shape: Shape) extends Shape { require(shape != null, "null shape in location") }
case class Group(shapes: Shape*) extends Shape { require(shapes != null, "null shape in location") // TODO is this sufficient? }
val r = Rectangle(20, 40) val q = Rectangle(20, 40) val p = Rectangle(20, 30)
val g = Group(r, Group(q, p), Location(10, 15, r))
def countGroup(s: Shape): Int = s match { case Rectangle(w, h) => 0 case Location(x, y, c) => ??? case Group(shapes @ _*) => ??? }
countGroup(r) @channel As I mentioned, the ??? is a placeholder for "not yet implemented". So countGroup(r) returns 0 as expected. But it would raise a NYI exception for group or location nodes. Now we need to apply recursive thinking: For location, the child might have group nodes. For group, the current node is a group node, plus the children might have group nodes. Hence... def countGroup(s: Shape): Int = s match { case Rectangle(w, h) => 0 case Location(x, y, c) => countGroup(c) case Group(shapes @ *) => var sum = 1 for (c <- shapes) sum += countGroup(c) sum } Now countGroup(g) returns 2 as expected. This is a Java-style, imperative implementation. @channel Or equivalently using the foreach method instead of the so-called for comprehension: case Group(shapes @ ) => var sum = 1 shapes.foreach { c => sum += countGroup(c) } sum @channel Now...drum roll...the opportunity to make it functional/applicative/immutable. case Group(shapes @ _) => 1 + shapes.map { c => countGroup(c) } .sum where map transforms each item in a collection with the result of applying the given function to the item (which design pattern is that?) and sum adds all the items in a collection. @channel How would you compare these three implementations in terms of whatever criteria you can think of?