CS 334: HW 7

Instructions

This homework has three types of problems:

  • Self Check: You are strongly encouraged to think about and work through these questions, but you will not submit answers to them.

  • Problems: You will turn in answers to these questions.

  • Programming: This part involves writing Scala code. You are required to work with a partner on it.

    You are welcome to choose your own partner, but I can also assist in matching pairs --- simply send me a Slack message and I will pair you with someone else also looking for a partner. Please do not let finding a partner go until the last minute.

Reading

Self Check

1. Smalltalk Run-time Structures

Mitchell, Problem 11.4

The given conversions between Cartesian and polar coordinates work for any point (x,y), where x \geq 0 and y > 0. Do not worry about points where x < 0 or y \leq 0. The figure P.11.4.1 appears on page 332.

You should try to write reasonably accurate Smalltalk code for part (b), but you do not need to use Squeak.

Problems

1. Removing a Method (10 points)

Mitchell, Problem 11.7

2. Protocol Conformance (15 points)

Mitchell, Problem 11.6

(You will find it useful to answer Problem 11.7 first before working on this one.)  

3. Subtyping and Binary Methods (10 points)

Mitchell, Problem 11.8

4. Delegation-Based OO Languages (15 points)

Mitchell, Problem 11.9

Programming

See instructions here for setting up Scala.

The scala command will give you a "read-eval-print" loop, as in Lisp and ML.

You can also compile and run a whole file as follows. Suppose file A.scala contains:

object A {
    def main(args : Array[String]) : Unit = {
        println(args(0));
    }
}

You then compile the program with scalac A.scala, and run it (and provide command-line arguments) with "scala A moo cow".

Resources

There is plenty of very detailed information about Scala available online (e.g., http://www.scala-lang.org --- just web search for "Scala Language"). I suggest that you look at tutorial-style descriptions of the features of interest as well as the Scala Language Specification for some of the specifics.

There is also extensive online documentation for the Scala libraries at http://www.scala-lang.org/api/.

1. The Happy Herd (15 points)

In this first question we'll use Scala answer a few questions about cows. Specifically the herd at Cricket Creek Farm...

  1. First, write a program to read in and print out the data in the file "cows.txt" your gitlab repository for hw7. Each line contains the id, name, and daily milk production of a cow from the herd. (I've also included a "cows-short.txt" file that may be useful while debugging.)

    The program should be in a file called "Cows.scala" that includes a single object definition. (I didn't give you this in your repo -- just create it yourself and it add with git add Cows.scala.) Recall that objects are like classes, except that only a single instance is created. You file should be something like:

    object Cows {
        def main(args : Array[String]) : Unit = {
            // ...
        }
    }
    

    One useful snippet of code is the following line.

    val lines = scala.io.Source.fromFile("cows.txt").getLines();
    

    We will use this to read the file. Try this out in the Scala interpreter. What type does lines have? For convenience in subsequent processing, it will be useful to convert line into a list:

    val data = lines.toList;
    

    Print out the list and verify you are successfully reading all the data. Use a for loop. For loops in Scala follow a familiar syntax:

    scala> for (i <- 1 to 3) println(i);
    1
    2
    3
    
  2. Print the data again, using the foreach method on lists.

  3. The for construct lets you do many other things as well, such as selectively filtering out the elements while iterating. For example:

    scala> for (i <- 1 to 5 if i%2==0) println(i);
    2
    4
    

    Use such a for list to print all cows containing "s" in their name. Make the test be case insensitive. Scala Strings support all of the same string operations as Java Strings. A few useful ones here and below:

    class String {
        def contains(str : String) : Bool
        def startsWith(str : String ) : Bool
        def toLowerCase() : String
        def toUpperCase() : String
    
        // split breaks up a line into pieces separated by separator.
        //   For ex:   "A,B,C".split(",")  ->  ["A", "B", "C"]
        def split(separator : String) : Array[String] 
    }
    
  4. Now print all cows containing "s" but not "h". Multiple if clauses can be chained together, as in "1 to 10 if i%2==0 if i%3==0".

  5. Scala also supports list comprehensions:

    val list = ...;
    println (for (x <- list if ...) yield f(x));
    

    Show an example of list comprehensions by computing something about the data with one. (You may need to look up list comprehensions in the documentation for more detail...)

  6. Next, define a new class in "Cows.scala" to store one cow, its id, and its daily milk production.

    class Cow(s : String) {
        def id = ... 
        def name = ... 
        def milk = ...
        override def toString() = { 
            ...
        }
    }
    

    It takes in a string of the form "id,name,milk" from the data file and provides the three functions shown. For toString, you may find formatting code like ""%5d ".format(n)" handy -- it formats the number n as a string and pads it to 5 characters.

    Use a map operation on data to convert it from a list of strings to a list of Cows. Print the data and makes sure it works.

  7. Use a list comprehension to print all cows who produce more then 4 gallons of milk per day.

  8. Use the sortWith method on Lists to sort the cows by id. Also use foldLeft to tally up the milk production for the whole herd.

    class List[A] {
        def sortWith (lt: (A, A) => Boolean) : List[A]
        def foldLeft [B] (z: B)(f: (B, A) => B) : B
    }
    

    Note that foldLeft is a polymorphic method with type parameter B. Also, foldLeft is curried, so you must call it specially, as in:

    val list : List[Int] = ...;
    val n : Int = ...;
    list.foldLeft (n) ( (x: Int, elem: Int) => ... )
    
  9. Finally, use the maxBy and minBy methods on your list of cows to find the cows with the highest and lowest daily milk production.

2. Ahoy. World! (15 points)

You'll now learn to speak like a pirate, with the help of Scala maps and a Translator class... The program will take in an English sentence and convert it into pirate. For example, typing in

pardon, where is the pub?

gives you

avast, whar be th' Skull & Scuppers?

The handouts page contains a "Pirate.scala" file to start with. You will be responsible for implementating a Translator class, reading in the priate dictionary, and processing the user input. It will be easiest to proceed in the following steps:

  1. First, complete the Translator class. It has the following signature:

    class Translator {
        // Add the given translation from an English word
        //    to a pirate word
        def += (english : String, pirate : String) : Unit
    
        // Look up the given english word.  If it is in the dictionary, 
        //   return the pirate equivalent.  
        // Otherwise, just return English.  
        def apply(english : String) : String
    
        // Print the dictionary to stdout
        override def toString() : String
    }
    

    Note that we're overloading the += and () operators for Translator. Thus, you use a Translator object as follows:

    val pirate = new Translator();
    pirate += ("hello", "ahoy");
    ..
    val s = pirate("hello");
    

    If "hello" is in the dictionary, its pirate-translation is returned. Otherwise, your translator should return the word passed in. Any non-word should also just be returned. Thus:

    pirate("hello")   ==>  "hello"
    pirate("moo")     ==>  "moo"
    pirate(".")       ==>  "."
    

    When writing apply, use the get method on map and pattern matching to handle the Option type it returns. (See class notes / tutorial for details on Option.)

    Finish the definition of Translator using a Scala map instance variable. To write toString, you may find it handy to look at the mkString methods of the Scala Map classes.

    Add a few lines to the Pirate main method to test your translator.

  2. Now, read in the full pirate dictionary from the "pirate.txt" data file, and print out the resulting translator.

  3. Once you have the translator built, uncomment the lines in main that process standard input, and process the text the user types in. There are a few sample sentences on the handouts page. Here is an example:

    Stephen-Freund:~/scala] cat sentence1.txt 
    pardon, where is the pub?
    I'm off to the old buried treasure.
    
    Stephen-Freund:~/scala] scala Pirate < sentence1.txt
    avast, whar be th' Skull & Scuppers?
    I'm off to th' barnacle-covered buried treasure.
    
  4. You may still get some deprecation warnings to the effect of "multiarg infix syntax looks like a tuple and will be deprecated". Typically I'd make sure the code produces no warnings, but let's not worry about it this week...

3. Argh, Expressions Matey (20 points)

Using Scala 2.13+ on your own computer?

You may encounter one small issue if you are using Scala 2.13+ on your own computer.
Specifically, a library my starter code uses is no longer included with the compiler. Please do the following if you run into compilation problems related to import statements:

  • Download the following and save in your project directory: scala-parser-combinators.jar

  • Compile and run your code with:

    scalac -cp .:scala-parser-combinators.jar Expressions.scala 
    scala -cp .:scala-parser-combinators.jar Expressions
    

    (Note: if you are on a windows machine, use .;scala-parser-combinators.jar as the classpath.)

You may ignore any deprecation warnings.

In Scala, algebraic datatypes can be defined with the use of abstract classes and case classes. Consider the following algebraic data type for expressions:

sealed abstract class Expr 
case class Variable(name: Symbol) extends Expr 
case class Constant(x: Double) extends Expr 
case class Sum(l: Expr, r: Expr) extends Expr 
case class Product(l: Expr, r: Expr) extends Expr 
case class Power(b: Expr, e: Expr) extends Expr

This Scala code is equivalent to the following definition in ML:

datatype Expr = 
    Variable of Symbol 
    | Constant of double
    | Sum of Expr * Expr 
    | Product of Expr * Expr 
    | Power of Expr * Expr;

Have a look at the starter code in Expressions.scala to see an example of Scala-style pattern matching on case classes.

  1. Write a function that takes the derivative of an expression with respect to a given variable. Your function should have the following signature:

    def derive(e: Expr, s: Symbol): Expr
    

    Your function does not have to take the derivative of Powers with non-constant exponents. It is acceptable to throw an exception in that circumstance.

    Also, you'll likely need the Chain Rule for the Power case, though you may further restrict your code to handle only cases where the base is a variable or constant for simplicity if you prefer.

    You can find various derivative rules here.

    Note that in Scala, Symbols can be declared by using a single quote before the name of the symbol, as such:

    scala> 'x 
    res0: Symbol = 'x
    
    scala> 'y 
    res1: Symbol = 'y
    
    scala> 'abc 
    res2: Symbol = 'abc
    

    Tests

    The main method for Expressions contains a number of test cases for derive and the other methods you will write. Uncomment those tests as you implement the methods. Those are the cases the autograder uses, so if you pass them you should be all set.

    You will find a description of how those tests work below.

  2. Write a function that evaluates a given expression in a given environment. An environment is just a mapping from symbols to values for those symbols. Your function should have the following signature:

    def eval(e: Expr, env: Map[Symbol, Double]): Double
    

    If a variable in the expression is not in the given environment, you should throw an exception.

  3. Write a function that when given an expression reduces that expression to its simplest form. Your function should have the following signature:

    def simplify(e: Expr): Expr
    

    For example,

    simplify(Sum(Variable('x),Constant(0)))
    

    should return Variable(’x). Your function need not be exhaustive -- just implement four or five interesting cases.

Testing with assertEquals

In order to make the task of writing tests easier, we provide an expression parser. The expression parser takes a string and returns its corresponding Expr. The expression parser can be invoked on a string str like so: Expr(str). For example, to demonstrate that your simplifier knows about the additive identity of zero, you might write the following test:

assertEquals(Expr("x"), simplify(Expr("x + 0")))

The syntax that the expression parser accepts can be expressed by the following grammar:

             expr := sum 
              sum := product { ("+" | "-") product } 
          product := power { "*" power } 
            power := factor [ "^" factor ] 
           factor := "(" expr ")" | variable | constant 
         variable := ident constant := floatLit 
         floatLit := [ "-" ] positiveFloat 
    positiveFloat := numericLit [ "." [ numericLit ] ]

Submitting Your Work

Submit your homework via GradeScope by the beginning of class on the due date.

Written Problems

Submit your answers to the Gradescope assignment named, for example, "HW 1". It should:

  • be clearly written or typed,
  • include your name and HW number at the top,
  • list any students with whom you discussed the problems, and
  • be a single PDF file, with one problem per page.

You will be asked to resubmit homework not satisfying these requirements. Please select the pages for each question when you submit.

Programming Problems

If this homework includes programming problems, submit your code to the Gradescope assignment named, for example, "HW 1 Programming". Also:

  • Be sure to upload all source files for the programming questions, and please do not change the names of the starter files.
  • If you worked with a partner, only one of each pair needs to submit the code.
  • Indicate who your partner is when you submit the code to gradescope. Specifically, after you upload your files, there will be an "Add Group Member" button on the right of the Gradescope webpage -- click that and add your partner.

Autograding: For most programming questions, Gradescope will run an autograder on your code that performs some simple tests. Be sure to look at the autograder output to verify your code works as expected. We will run more extensive tests on your code after the deadline. If you encounter difficulties or unexpected results from the autograder, please let us know.