Lab 08: Polymorphism
8.1. Companion objects and Option
Consider the following implementation of the type Nat
, which you are well-familiar with:
trait Nat { def +(other: Nat): Nat } case object Zero extends Nat{ override def +(other: Nat): Nat = other } case class Succ(n: Nat) extends Nat{ override def +(other: Nat): Nat = Succ(n + other) }
It might be the case that we would like to use nat values from different sources, e.g. fromInteger(1) + Zero + fromString(“2”)
. Keeping track of all different functions that convert other values to Nat might be tedious. A work-around is to: (1) define a companion object for Nat by creating a new object with the same name as Nat
, in the same scala file (or worksheet).
object Nat { // a companion object for trait Nat }
We can now populate the companion objects with any methods we would like. In particular, we can add a method:
def apply(i: Int): Nat = ???
which can be called simply as: Nat(5)
. Also, we can (2) overload apply
in order to support creating naturals with the call Nat(.)
from different other types.
8.1.1. Create a companion object for Nat
and add appropriate apply functions in order to make the expression Nat(1) + Zero + Nat(“2”)
successfully compile and run.
8.1.2. Define a function that takes a list of integers and converts them to a list of type List[Option[Nat]]
. If an integer x
is positive, it should become a value Some(x)
. Otherwise it should become None
.
def fromList(l: List[Integer]): List[Option[Nat]] = ???
8.1.3. Define a function which takes a list of Option[Nat]
and converts it to Option[List[Nat]]
. The logic here is that if the list contains at least one None
value (hence some conversion from integer failed), then the result should be None
. Otherwise, it should be a list containing all valid naturals:
def fromOptions(l: List[Option[Nat]]): Option[List[Nat]] = ???
8.2. Dictionaries
Create a class Dictionary
(which mimics Scala's Map
), starting from the following template. A map takes as parameter a list of key-value pairs. (Other, more efficient implementations are possible).
class Dictionary[K,V](inner: List[(K,V)]) { }
8.2.1. Create a companion object for Dictionary. Create an apply method which creates a dictionary from a list. For now, it's sole purpose is to hide from the programmer the usage of the keyword new
when creating a new dictionary, but we will use the companion also later on.
8.2.2. Create and implement member functions +
(adds a new key-value pair to the Dictionary), contains
and containsKey
. Use higher-order functions.
8.2.3. Implement the method get
which retrieves a value iff it exists:
def get(key: K): Option[V] = ???
8.2.4. Implement the method getOrElse
which returns a default value iff a key is non-existent in the map:
def getOrElse(default: V)(key: K): V = ???
8.2.5. Implement map
as a member function for dictionaries. Think about how should map
work over a dictionary.
8.2.6. In some situations, we would like to use maps that always return the same default value for a non-existent key. For this reason, implement:
def withDefaultValue(default: V): Dictionary[K,V] = ???
In order to implement it, you need to create a new type of dictionary that stores a default value. The easiest way to implement this, is to modify the class Dictionary
as follows:
class Dictionary[K,V](inner: List[(K,V)], default: Option[V]) { }
if default
is None
, then the dictionary behaves exactly as before. On the other hand, if default
is Value(v)
, then the method get
will return the default value. Careful: The function map
should also modify the default value!
8.2.7. Modify the companion object of Dictionary
such that the class constructor is concealed: define two apply methods, one for no default value, and one for dictionaries with a given default value.
8.3. Polynomials
In this section, we can resort to the datatype Map
instead of your previously-defined Dictionary
.
Consider a polynomial encoded as a map, where each present key denotes a power of X, and a value denotes its coefficient.
Map(2 -> 2, 0 -> 3) // encodes 2*X^2 + 3
Add to your worksheet, the definition below:
case class Polynomial (nonZeroTerms: Map[Int,Int])
8.3.1. Add a method *
which multiplies the polynomial by a given coeficient:
def *(n: Int): Polynomial = ???
8.3.2. Override the toString
method to nicely display a polynomial. The output should be ordered from the highest-ranking polynomial, and terms with the zero-coeficient should not be displayed.
override def toString: String = ???
8.3.3. Implement a method hasRoot
which checks if a given integer is a root of the polynomial.
def hasRoot(r: Int): Boolean = ???
8.3.4. Implement a method degree
which returns the degree of the polynomial.
def degree: Int = ???
</code>
8.2.5. Implement a method +
which adds a given polynomial to this one. Hint - you can use zip, but other efficient options are possible.
def +(p2: Polynomial): Polynomial = ???
8.2.6. Implement a method *
which multiplies polynomials. Hint: this operation is very similar to cartesian product.
def *(p2: Polynomial): Polynomial = ???