Programming Assignment #6 (190 Points)Due: Fri, Mar 14, 5:00pmThe overall objective of this assignment is to introduce you to some advanced features of Scala, including case classes, implicits, and randomized property checking. To start, download pa6.zip and run unzip pa6.zip. This will unleash three files containing several skeleton Scala functions, with missing bodies denoted by the placeholder textsys.error("TO BE DONE") . Your
task is to replace the placeholder text in those files with the appropriate
Scala code for each case.
The zipfile also contains the following helper files that you will not modify: And some sample output files for Problem #2:Assignment Testing and EvaluationSame as for Homework #5. Submission InstructionsTo turn in your solution, simply type $ make turninin the directory in which you are working. turnin will provide you with a confirmation of the submission process; make sure that the size of the file indicated by turnin matches the size of your zip file. ScalaCheck JAR FileWriting tests can be tedious. To help reduce the burden, in this assignment we will learn to use ScalaCheck, a library for automatic property checking based on random test generation! As you can see from the Makefile, we have downloaded the ScalaCheck JAR file as /cs130w/public/scalacheck_XXX.jar and added this file to the class path for compilation. Furthermore, we have defined an alias for scala that includes the ScalaCheck JAR file on the classpath. So, you should be able to access the ScalaCheck library from the REPL simply by running scala. To verify that this alias has been preped correctly, try running: $ which scala alias scala= ’scala -cp .:/home/linux/ieng6/cs130w/public/scalacheck_2.10-1.11.3.jar’ /home/linux/ieng6/cs130w/public/bin/scala Problem #1: Case Classes (Bst.scala)For this problem, you will use Scala’s case classes to implement an Abstract Set datatype, whose properties are captured by the following trait: trait AbstractSet[A] extends Iterable[A] { def contains(n: A): Boolean def add(n: A): AbstractSet[A] def remove(n: A): AbstractSet[A] // For Testing def execCmds(cmds: List[(Boolean, A)]): AbstractSet[A] = { cmds.foldLeft(this)((s, cmd) => { if (cmd._1) { this.add(cmd._2) } else { this.remove(cmd._2) } }) } }This trait includes the usual set operations which you will implement, and methods that uses add and remove for testing (we’ll see this later.)
Sets as Binary Search TreesTo implement the specification above, we will represent sets as Binary Search Trees that support efficient addition and removal of elements. We represent the datatype as: case class Node[A](elt: A, left: BST[A], right: BST[A]) extends BST[A] case class Leaf[A]() extends BST[A] sealed abstract class BST[A] extends AbstractSet[A](The real code includes the implicits that allow for comparisons.)
Thus, values of type
We will only use trees that are binary search ordered, which is to say,
trees
Note: Make sure you understand what the (A) 10 points: Build
Fill in the definition of the method
We will now use ScalaCheck for the first time to randomly generate
a bunch of test cases for our scala> BstProperties.prop_bso.check + OK, passed 100 tests. (B) 10 points: ContainsNext, fill in the definition of the method def contains(x: A): Booleanso that t.contains(x) returns true iff the
element x is in the tree t . When you
are done, you should see the following behavior at the REPL:
scala> BST.t2 res6: BST[Int] = Node(5, 10, 20, 30) scala> List(5,6,10,11,20,21,30,31).map(BST.t2 contains _) res7: List[Boolean] = List(true, false, true, false, true, false, true, false) scala> BstProperties.prop_contains_elt.check + OK, passed 100 tests. (C) 15 points: FoldNow, write a fold method that performs an in-order traversal of the tree, accumulating the results as it goes. def fold[B](f: (B, A) => B, acc: B): BWhen you are done, various bonus properties of the tree get unlocked by virtue of the iterator method which we have supplied that uses
fold . In particular, you should get the following behavior:
cala> BST.t2.toList res11: List[Int] = List(5, 10, 20, 30) scala> BST.t2.toString res13: String = BST(5, 10, 20, 30)You should also check the property that the trees you build actually contain the elements from the source list. scala> BstProperties.prop_contains_elts.check + OK, passed 100 tests. (D) 10 points: AddNow fill in the definition for def add(x:A): BST[A]which returns a (new) BST which has the same elements as the original
tree plus the new element x . Of course, the new tree should also satisfy
the binary-search-ordering invariant. If the x is already in the
tree, then the output tree should be the same as the original.
When you are done, you should see the following behavior:
scala> BST.t2 res0: BST[Int] = BST(5, 10, 20, 30) scala> BST.t2.add(12) res1: BST[Int] = BST(5, 10, 12, 20, 30) scala> val t2_new = BST.t2.add(12) t2_new: BST[Int] = BST(5, 10, 12, 20, 30) scala> t2_new.isOrdered res2: Boolean = trueAnd a few more properties (make sure to read and understand them): Scala> BstProperties.prop_add_elt.check + OK, passed 100 tests. scala> BstProperties.prop_add_elts_old.check + OK, passed 100 tests. (E) 5 points: Debug the PropertyWe’ve seen a bunch of properties go sailing through. But consider this: Scala> BstProperties.prop_multiset.check ! Falsified after 5 passed tests.Uh oh... Let’s try to debug the code and property to see why the test fails. In the Scala shell, you should see something like this: scala> BstProperties.prop_multiset.check ! Falsified after 4 passed tests. > ARG_0: List("1", "1")You might see something different for ARG_0 (i.e. some other
particular list).
Let’s see why the property failed by running the test on the failing input
that ScalaCheck has automatically found for us!
First, copy that input into a variable: scala> val xs = List(1, 1) xs: List[Int] = List(1, 1)(Again, your failing input may be slightly different, because ScalaCheck finds them by random sampling.) Now, let’s run the property on that input. The property, defined in Bst.scala, is: forAll((xs: List[Int]) => BST(xs).toList == xs.sorted)That is, for any list xs , the result of building a
BST from xs and converting it to a list should be
identical to just sorting the list xs .
Let’s see if that’s true for the particular xs that we have above.
scala> BST(xs).toList == xs.sorted res2: Boolean = falseHmm, why? Well, dig further. The left hand side of the equality is scala> BST(xs).toList res3: List[Int] = List(1)and the right hand side is scala> xs.sorted res4: List[Int] = List(1, 1)of course! The BST does not keep duplicates
but vanilla sorting does!
So the property itself is broken.
Instead, we want to check only that the BST
has all the elements from xs without any duplicates.
Fix the property so that it passes.
Do this in a meaningful way – don’t just replace it with Note: You can use the above strategy to fix your code when there are other failing tests, too. It just so happens that in this case, the property itself was "wrong". :-) (F) 10 points: Remove MinWe also want to remove elements from the set. As a first step, write a method def removeMin : (A, BST[A])that returns a tuple of the minimum element in the tree and the tree containing all elements except the minimum. If the tree passed in is empty there is no minimum element, so use sys.error to indicate the problem.
When you are done you should see this behavior:
Scala> BST.t2.removeMin res7: (Int, BST[Int]) = (5,BST(10, 20, 30)) or better, with the properties Scala> BstProperties.prop_remove_min.check + OK, passed 100 tests. (G) 20 points: Remove
Finally, use def remove(x: A): BST[A]which returns the set containing all elements except x .
Of course, the new set should satisfy the binary-search-ordering property.
When you are done, you should see the following behavior:
scala> BST.t2.remove(12) res1: BST[Int] = BST(5, 10, 20, 30) scala> BST.t2 res2: BST[Int] = BST(5, 10, 20, 30) scala> BST.t2.remove(20) res3: BST[Int] = BST(5, 10, 30)And we can stress test with properties: scala> BstProperties.prop_remove_elt.check + OK, passed 100 tests. scala> BstProperties.prop_remove_elt_old.check + OK, passed 100 tests. scala> BstProperties.prop_bst_equiv_gold.check + OK, passed 100 tests. Problem #2: Decorators (Decorators.scala)In Scala, functions are (also) objects. In this exercise, you will see an example of the decorator design pattern, where we add functionality to objects via wrappers.
In the file Decorators.scala there is an example decorator
def apply[A, B](name: String)(f: A => B) = new Function1[A, B] {...}takes two arguments, a name string and a function f
and returns a new function object whose apply method behaves just
like f except that, at each call, it increments the
count associated with the name .
Thus, to find out how many times a decorated
function named name has been called,
we can simply call profile.count(name) .
After running $ makeyou should see the following behavior at the Scala REPL: scala> import Decorators._ scala> import DecoratorTest._ scala> profile.count("fac") res0: Int = 0 scala> fac(5) res1: Int = 120 scala> profile.count("fac") res2: Int = 5 scala> fac(10) res3: Int = 3628800 scala> profile.count("fac") res4: Int = 15 scala> profile.reset("fac") scala> profile.count("fac") res6: Int = 0 scala> fac(4) res7: Int = 24 scala> profile.count("fac") res8: Int = 4 (A) 20 points: Tracing
Complete the definition for the decorator
When you are done, you should see the following behavior: scala> import DecoratorTest._ scala> fibT(5) ,- fibT(5) | ,- fibT(4) | | ,- fibT(3) | | | ,- fibT(2) | | | | ,- fibT(1) | | | | `- 1 | | | | ,- fibT(0) | | | | `- 1 | | | `- 2 | | | ,- fibT(1) | | | `- 1 | | `- 3 | | ,- fibT(2) | | | ,- fibT(1) | | | `- 1 | | | ,- fibT(0) | | | `- 1 | | `- 2 | `- 5 | ,- fibT(3) | | ,- fibT(2) | | | ,- fibT(1) | | | `- 1 | | | ,- fibT(0) | | | `- 1 | | `- 2 | | ,- fibT(1) | | `- 1 | `- 3 `- 8 res9: Int = 8 scala> even(5) ,- even(5) | ,- odd(4) | | ,- even(3) | | | ,- odd(2) | | | | ,- even(1) | | | | `- false | | | `- false | | `- false | `- false `- false res10: Boolean = false (B) 10 points: Tracing with ExceptionsExtend your tracing function to handle exceptions nicely. If an exception occurs in the function, the nesting level must be adjusted to the appropriate level where the exception is caught.
Note: Scala reuses Java’s exception mechanism, so an exception is simply
an object of type When you are done, you should get the following behavior: scala> changeT(List(5, 3), 6) ,- changeT((List(5, 3),6)) | ,- changeT((List(5, 3),1)) | | ,- changeT((List(3),1)) | | | ,- changeT((List(),1)) | ,- changeT((List(3),6)) | | ,- changeT((List(3),3)) | | | ,- changeT((List(3),0)) | | | `- List() | | `- List(3) | `- List(3, 3) `- List(3, 3) (C) 20 points: Memoize
Next, complete the definition of the
To this end, you need to add a private cache to the wrapped function object. In
the
Once you have implemented the function, you should get the following behavior at the prompt: scala> import DecoratorTest._ scala> snooze(0) // 5 second pause res0: Int = 1 scala> snooze(0) // practically instantaneous res1: Int = 1 (D) 10 points: Memoize with Exceptions
Extend the
When you are done you should see the following behavior at the REPL: scala> import DecoratorTest._ scala> hissy(0) // 5 second pause res0: Boolean = true scala> hissy(1) // 5 second pause DecoratorTest$HissyFitException at DecoratorTest$anonfun$16.apply$mcZI$sp(Test.scala:123) at DecoratorTest$anonfun$16.apply(Test.scala:121) ... scala> hissy(0) // practically instantaneous res1: Boolean = true scala> hissy(1) // practically instantaneous DecoratorTest$HissyFitException at DecoratorTest$anonfun$16.apply$mcZI$sp(Test.scala:123) at DecoratorTest$anonfun$16.apply(Test.scala:121) ... TestingWhen you have finished this entire problem, you can test your solution as follows: $ make decorators $ scala DecoratorTest > out $ diff out decorators.out $That is, the output file out should be identical to the supplied, reference output file decorators.out. Note: When debugging the difference between your out file and decorators.out, you may find it helpful to run the following for a side-by-side comparison: $ vimdiff out decorators.out Problem #3: Document Layout (Json.scala)For this problem, you will write a simple document layout engine that allows nested documents to be printed cleanly. A document is represented by an instance of the class: class Doc(val lines: List[String])Thus, a document is a list of lines, each of which is a string. For example, the document ruination whiterascal fattireis represented as Doc(List("ruination", "whiterascal", "fattire")) .
We have also provided implementations of methods for computing:
width ), we
refer to the document as a box document.
(A) 5 points: Padding
Fill in the definition of
When you are done, you should see the following behavior at the prompt. scala> Doc.padBegin(List(1,2,3,4,5), 10, 0) res: List[Int] = List(0,0,0,0,0,1,2,3,4,5) scala> Doc.padBegin(List(1,2,3,4,5), 3, 0) res: List[Int] = List(1,2,3,4,5) (B) 5 points: More Padding
Fill in the definition of When you are done, you should see the following behavior at the prompt. scala> Doc.padEnd(List(1,2,3,4,5), 10, 0) res: List[Int] = List(1,2,3,4,5,0,0,0,0,0) scala> Doc.padEnd(List(1,2,3,4,5), 3, 0) res: List[Int] = List(1,2,3,4,5) (C) 5 points: Left-Aligned Vertical Concat
Fill in the implementation of method
When you are done, you should see the following behavior at the prompt.
Note: The provided scala> val d0 = Doc("cat") vcat Doc("mongoose") d0: Doc = [cat ] [mongoose] scala> Doc("cat") vcat Doc("mongoose", "mouse") res0: Doc = [cat ] [mongoose] [mouse ] (D) 10 points: Top-Aligned Horizontal Concat
Next, fill in the implementation of method When you are done, you should see the following behavior at the prompt. scala> val dks = Doc("1: ", "2: ", "3: ") scala> val dvs = Doc("cat", "doggerel", "moose") scala> dks hcatT dvs res0: Doc = [1: cat ] [2: doggerel] [3: moose ] scala> Doc("{ ") hcatT dks hcatT dvs res1: Doc = [{ 1: cat ] [ 2: doggerel] [ 3: moose ] scala> dvs hcatT Doc(" ") hcatT dks res2: Doc = [cat 1: ] [doggerel 2: ] [moose 3: ] scala> dvs hcatT Doc("---> at the top") res3: Doc = [cat ---> at the top] [doggerel ] [moose ] (E) 10 points: Bottom-Aligned Horizontal Concat
Next, fill in the implementation of method When you are done, you should see the following behavior at the prompt. scala> val dks = Doc("1: ", "2: ", "3: ") scala> val dvs = Doc("cat", "doggerel", "moose") scala> dks hcatB dvs res5: Doc = [1: cat ] [2: doggerel] [3: moose ] scala> Doc("{ ") hcatB dks hcatB dvs res6: Doc = [ 1: cat ] [ 2: doggerel] [{ 3: moose ] scala> dks hcatB dvs hcatB Doc(" }") res7: Doc = [1: cat ] [2: doggerel ] [3: moose }] scala> dvs hcatB Doc(" ") hcatB dks res8: Doc = [cat 1: ] [doggerel 2: ] [moose 3: ] scala> dvs hcatB Doc("---> at the bottom") res9: Doc = [cat ] [doggerel ] [moose ---> at the bottom] Testing
When you have both scala> val d0 = Doc("cat") vcat Doc("mongoose") scala> val d1 = d0 hcatT Doc(" ") hcatT d0 hcatT Doc(" ") hcatT d0 d1: Doc = [cat cat cat ] [mongoose mongoose mongoose] scala> val d2 = Doc("apple") vcat d1 d2: Doc = [apple ] [cat cat cat ] [mongoose mongoose mongoose] scala> val d3 = d2 hcatT Doc(" ") hcatT d2 d3: Doc = [apple apple ] [cat cat cat cat cat cat ] [mongoose mongoose mongoose mongoose mongoose mongoose] Problem #4: JSON Rendering (Json.scala)JavaScript Object Notation (JSON) is a lightweight data-interchange format, that is human- and machine-readable, and commonly used in so-called AJAX web applications like Gmail, Facebook, etc. to ship data across machines.
We have defined a Scala class sealed abstract class JVal case class JStr(s: String) extends JVal case class JNum(n: BigDecimal) extends JVal case class JObj(o: Map[String, JVal]) extends JVal case class JArr(a: List[JVal]) extends JValInformally, a JVal is one of:
JVal s to pretty Doc s,
which can be shown as String s in JSON format.
(A) 15 points: Rendering
Use the def render(jv: JVal) : Doc When you are done, you should see the following behavior at the prompt: scala> import JsonTest._ scala> JVal.render(jvReals(0)) res0: Doc = [{ fst : 1 ] [, snd : cat }] scala> JVal.render(jvReals(1)) res1: Doc = [{ fst : { fst : 1 ] [ , snd : cat } ] [, snd : { fst : 1 ] [ , snd : cat } ] [, thd : { fst : 1 ] [ , snd : cat } }] scala> JVal.render(jvReals(2)) res2: Doc = [{ fst : 1 ] [, snd : cat ] [, thd : { fst : { fst : 1 ] [ , snd : cat } ] [ , snd : { fst : 1 ] [ , snd : cat } ] [ , thd : { fst : 1 ] [ , snd : cat } } }] scala> JVal.render(jvReals(3)) res3: Doc = [{ fst : 1 ] [, snd : { fst : 2 ] [ , snd : { fst : 3 ] [ , snd : { fst : 4 ] [ , snd : { fst : 5 ] [ , snd : Nil } } } } }] scala> JVal.render(jvReals(4)) res4: Doc = [[ { fst : one ] [ , snd : 1 } ] [, { fst : two ] [ , snd : 2 } ] [, { fst : three ] [ , snd : 3 } ]] scala> JVal.render(jvReals(5)) res5: Doc = [[ 1 ] [, 2 ] [, 3 ] [, 4 ]] scala> JVal.render(jvReals(6)) res6: Doc = [{ mon : 10 ] [, tue : 20 ] [, wed : 30 }] scala> JVal.render(jvReals(7)) res7: Doc = [{ eng : [ { fst : one ] [ , snd : 1 } ] [ , { fst : two ] [ , snd : 2 } ] [ , { fst : three ] [ , snd : 3 } ] ] [, spanish : [ { fst : uno ] [ , snd : 1 } ] [ , { fst : dos ] [ , snd : 2 } ] [ , { fst : tres ] [ , snd : 3 } ] ] [, french : [ { fst : un ] [ , snd : 1 } ] [ , { fst : deux ] [ , snd : 2 } ] [ , { fst : trois ] [ , snd : 3 } ] }]NOTE: The sample tests in Test.scala do not include the above examples. OPTIONAL Problem #5: Serializiation (Json.scala)NOTE: This problem will not be graded. It’s entirely up to you whether you want to get some experience working with a really cool feature of Scala.
It is rather tedious to write down We have defined a trait trait Json { def json: JVal }which represents values that are convertable to JVal
(in Haskell parlance, Json is a typeclass).
The object JsonWriter uses the above trait to define
methods that will serialize Scala values:
object JsonWriter { def write(v: JVal): String = JVal.render(v).toString def write[A <% Json](x: A): String = write(x.json) }The write method takes any input of type A
as long as A has an implicit view of
of the Json trait, and converts it to a string.
To do so, it uses the implicit
.json method to obtain a JVal ,
which is then rendered as a String .
For example, to render some Scala values to JSON, just run: scala> import Json._ //puts the implicit views into scope scala> 1.json res5: JVal = JNum(1) scala> "dog".json res6: JVal = JStr(dog) scala> (1,(2,(3,"Null"))).json res9: JVal = JObj(Map(fst -> JNum(1), snd -> JObj(Map(fst -> JNum(2), snd -> JObj(Map(fst -> JNum(3), snd -> JStr(Null) )))))) scala>Assuming your render function from before is working properly,
you should get:
scala> JsonWriter.write(1,(2,(3,"Null"))) res10: String = { fst : 1 , snd : { fst : 2 , snd : { fst : 3 , snd : "Null" } } }Note that in the last case, we are automatically converting a type for which we did not even define a JSON converter!!! Instead, Scala is composing the implicits for Int , String and
(A, B) in a type-directed manner.
(A) 0 points: Converting Scala Triples
Using the definition of implicit def tup3Json[A1 <% Json, A2 <% Json, A3 <% Json](p: (A1, A2, A3)) : JsonWhen you are done, you should see the following behavior at the prompt. scala> import Json._ scala> tup1.json res16: JVal = JObj(Map(fst -> JObj(Map(fst -> JNum(1), snd -> JStr(cat))), snd -> JObj(Map(fst -> JNum(1), snd -> JStr(cat))), thd -> JObj(Map(fst -> JNum(1), snd -> JStr(cat))))) scala> JsonWriter.write(tup1) res13: String = { fst : { fst : 1 , snd : "cat" } , snd : { fst : 1 , snd : "cat" } , thd : { fst : 1 , snd : "cat" } } scala> JsonWriter.write(tup2) res14: String = { fst : 1 , snd : "cat" , thd : { fst : { fst : 1 , snd : "cat" } , snd : { fst : 1 , snd : "cat" } , thd : { fst : 1 , snd : "cat" } } } (B) 0 points: Converting Scala ListsNext, fill in implicit def listJson[A <% Json](xs: List[A]) : JsonWhen you are done, you should see the following behavior at the prompt. scala> import Json._ scala> intl.json res17: JVal = JArr(List(JObj(Map(fst -> JStr(one), snd -> JNum(1))), JObj(Map(fst -> JStr(two), snd -> JNum(2))), JObj(Map(fst -> JStr(three), snd -> JNum(3))))) scala> JsonWriter.write(intl) res18: String = { { fst : "one" , snd : 1 } , { fst : "two" , snd : 2 } , { fst : "three" , snd : 3 } } (C) 0 points: Converting Scala ArraysNext, fill in implicit def arrJson[A <% Json](xs: Array[A]) : JsonWhen you are done, you should see the following behavior at the prompt. scala> import Json._ scala> ints.json res: JVal = JArr(List(JNum(1), JNum(2), JNum(3), JNum(4))) scala> JsonWriter.write(ints) res: String = { 1 , 2 , 3 , 4 } (D) 0 points: Converting Scala MapsFinally, fill in implicit def mapJson[A <% Json](m: Map[String, A]) : JsonWhen you are done, you should see the following behavior at the prompt. scala> import Json._ scala> mm.json res: JVal = JObj(Map(mon -> JNum(10), tue -> JNum(20), wed -> JNum(30))) scala> JsonWriter.write(mm) res: String = { mon : 10 , tue : 20 , wed : 30 } scala> JsonWriter.write(lang) res8: String = { eng : { { fst : "one" , snd : 1 } , { fst : "two" , snd : 2 } , { fst : "three" , snd : 3 } } , spanish : { { fst : "uno" , snd : 1 } , { fst : "dos" , snd : 2 } , { fst : "tres" , snd : 3 } } , french : { { fst : "un" , snd : 1 } , { fst : "deux" , snd : 2 } , { fst : "trois" , snd : 3 } } } |