Odersky's book, Chapter 6, Rational questions


#1

In Programming in Scala there is an example Rational class which begins

class Rational(n: Int, d: Int) {
require(d != 0)
private val g = gcd(n.abs, d.abs)
val numer = n / g
val denom = d / g

I just want to verify that g sticks around for the life of the object so that 3 values are stored instead of just 2. If space was more important than time, this would be more efficient:

class Rational(n: Int, d: Int) {
require(d != 0)
val numer = n / gcd(n.abs, d.abs)
val denom = d / gcd(n.abs, d.abs)

Also, this class works with Int, but what if I want to use Long? I can have a RationalInt class and a RationalLong class, but 99% of the code is duplicated. It seems like a templated Rational[T] would be effective, but not just any T has an abs function (among others). I can’t figure out how to use Integral or anything like it in the template. Can someone show a way to have

class Rational[?] { }
class RationalInt extends Rational[Int] { }
class RationalLong extends Rational[Long] { }

or something like that? Thanks.


#2

In Programming in Scala there is an example Rational class which
begins

class Rational(n: Int, d: Int) {
require(d != 0)
private val g = gcd(n.abs, d.abs)
val numer = n / g
val denom = d / g

I just want to verify that g sticks around for the life of the
object so
that 3 values are stored instead of just 2. If space was more
important
than time, this would be more efficient:

That’s correct, g would occupy the space of one Int.
You could also write

class Rational(n: Int, d: Int) {
  require(d != 0)
  val (numer, denom) = {
    val g = gcd(n.abs, d.abs)
    (n/g, g/d)
  }
}

although it might perform slightly worse due to the temporary
construction of a tuple. Or use a companion object

object Rational {
  def apply(n: Int, d: Int): Rational = {
    require(d != 0)
    val g = gcd(n.abs, d.abs)
    new Rational(n/g, g/d)
  }
}
class Rational private(val numer: Int, val denom: Int)

Probably this would be the best form.

It seems like a templated Rational[T] would be effective,
but not just any T has an abs function (among others). I can’t figure
out how to use Integral or anything like it in the template. Can someone
show a way to have

There are so-called type classes for numeric operations: Numeric
(addition, multiplication), Fractional (adds division), Integral (adds
modulus). You need Integral because gcd requires modulus.

There are default instances for Int, Long and others

implicitly[Integral[Long]] // -> scala.math.Numeric.LongIsIntegral

(http://www.scala-lang.org/api/current/scala/math/Numeric$.html)

These have the necessary math operations, e.g.

scala.math.Numeric.LongIsIntegral.rem(10L, 6L)  // 4

If you import the members of such an instance of Integral, you can use
’operators’ almost as before (because it provides extension methods
through mkNumericOps). So instead of

def gcd[A](a: A, b: A)(implicit num: Integral[A]): A = {
  if (b == num.zero) a else gcd(b, num.rem(a, b))
}

you can write

def gcd[A](a: A, b: A)(implicit num: Integral[A]): A = {
  import num._
  if (b == zero) a else gcd(b, a % b)
}

Then the whole class could be written

object Rational {
  def apply[A](n: A, d: A)(implicit num: Integral[A]): Rational[A] = {
    import num._
    require (d != zero)
    val g = gcd(n.abs, d.abs)
    new Rational(n / g, d / g)
  }

  private def gcd[A](a: A, b: A)(implicit num: Integral[A]): A = {
    import num._
    if (b == zero) a else gcd(b, a % b)
  }
}
class Rational[A] private (val numer: A, val denom: A)(implicit num:
Integral[A]) {
  import num._

  override def toString = s"$numer/$denom"

  def + (that: Rational[A]): Rational[A] =
    Rational(numer * that.denom + that.numer * denom,
             denom * that.denom)
}

Test:

Rational(4L, 8L) + Rational(5L, 9L)  // 19/18

best, .h.h.


#4

That’s awesome! I thought of the tuple method as well, but your solution with the companion object is much better. Although I knew about Integral, how to use it was a mystery. (implicit num: Integral[A]) must be further along in the book. Thanks!