A tour of Scala,
guided inspired by Spire

Arman Bilge
@armanbilge
[email protected]

Spire

  • I learn new languages by lots of reading
  • “Spire is a numeric library for Scala which is intended to be generic, fast, and precise.”
  • https://github.com/non/spire

About me

  • 5 years experience w/ Java, 2 years w/ Scala
  • Expertise is computational statistics applied to evolutionary biology
  • taking time off from PhD to focus on new venture

Abstracting over numerical types

  • Good code uses abstraction: prefer List<T> over ArrayList<T>, etc.
  • “Correct” numeric type not always obvious
  • int or long; float, double, or BigDecimal?

OO solution: inheritance

trait Multiplicable[T] {
def *(a: T): T
}
class MyInt(val i: Int) extends Multiplicable[MyInt] { ... }
class MyLong(val i: Long) extends Multiplicable[MyLong] { ... }
def square[T <: Multiplicable[T]](x: T): T = x * x
square(new MyInt(2))

Moving towards typeclasses

trait IsMultiplicable[T] {
def times(a: T, b: T): T
}
object IntIsMultiplicable extends IsMultiplicable[Int] {
def times(a: Int, b: Int) = a * b
}
object LongIsMultiplicable extends IsMultiplicable[Long] { ... }
def square[T](x: T, impl: IsMultiplicable[T]): T =
impl.times(x, x)
square(2, IntIsMultiplicable)

Implicits in Scala

  1. implicit conversions
implicit def intWrapper(x: Int) = new RichInt(x)
class RichInt(val self: Int) {
def until(end: Int): Range = Range(self, end)
}
for (i <- 0 until 10) println(i)
  1. implicit parameters
class Config(option: Boolean)
def f(x: String)(implicit cfg: Config) = { ... }
implicit config = new Config(false)
f("hello world")

Typeclasses

trait IsMultiplicable[T] {
def times(a: T, b: T): T
}
implicit object IntIsMultiplicable extends IsMultiplicable[Int] {
def times(a: Int, b: Int) = a * b
}
def square[T](x: T)(implicit impl: IsMultiplicable[T]): T =
impl.times(x, x)
square(2)

Boxing

  • Primitives vs references: balloons vs. strings to balloons
  • Java: int vs Integer; more transparent in Scala (Int)
  • Generics force boxing of primitives as objects

Boxed implementation

trait IsMultiplicable[T] {
def times(a: T, b: T): T
}
implicit object IntIsMultiplicable extends IsMultiplicable[Int] {
def times(a: Int, b: Int) = a * b
}
def square[T](x: T)(implicit impl: IsMultiplicable[T]): T =
impl.times(x, x)
square(2)

Boxed implementation

public final class Main$
{
public Object square(Object x, Main.IsMultiplicable impl)
{
return impl.times(x, x);
}
private Main$()
{
MODULE$ = this;
square(BoxesRunTime.boxToInteger(2), Main.IntIsMultiplicable..MODULE$);
}
public static Main$ MODULE$;
static
{
new Main$();
}
}

Boxed implementation

public static class Main$IntIsMultiplicable$
implements Main.IsMultiplicable
{
public int times(int a, int b)
{
return a * b;
}
public volatile Object times(Object a, Object b)
{
return BoxesRunTime.boxToInteger(times(BoxesRunTime.unboxToInt(a), BoxesRunTime.unboxToInt(b)));
}
}

Specialization

trait IsMultiplicable[@specialized T] {
def times(a: T, b: T): T
}
implicit object IntIsMultiplicable extends IsMultiplicable[Int] {
def times(a: Int, b: Int) = a * b
}
def square[@specialized T](x: T)(implicit impl: IsMultiplicable[T]): T =
impl.times(x, x)
square(2)

Specialized implementation

public final class Main$
{
public int square$mIc$sp(int x, Main.IsMultiplicable impl)
{
return impl.times$mcI$sp(x, x);
}
private Main$()
{
MODULE$ = this;
square$mIc$sp(2, Main.IntIsMultiplicable..MODULE$);
}
public static Main$ MODULE$;
static
{
new Main$();
}
}

Specialized implementation

public static class Main$IntIsMultiplicable$
implements sp
{
public int times(int a, int b)
{
return times$mcI$sp(a, b);
}
public int times$mcI$sp(int a, int b)
{
return a * b;
}
}

Nicer syntax

def uglySquare[@specialized T](x: T)(implicit impl: IsMultiplicable[T]): T =
impl.times(x, x)
class MultiplyOps[@specialized T](self: T)(implicit impl: IsMultiplicable[T]) {
def * (other: T): T = impl.times(self, other)
}
implicit def toMultiplyOps[@specialized T: IsMultiplicable](t: T): MultiplyOps[T] =
new MultiplyOps(t)
def square[@specialized T: IsMultiplicable](x: T) = x * x

Decompiling again

public final class Main$
{
public int square$mIc$sp(int x, Main.IsMultiplicable evidence$2)
{
return toMultiplyOps$mIc$sp(x, evidence$2).$times$mcI$sp(x);
}
}

Back to Spire

  • (Noting that Scala has a Numeric[T] typeclass)
  • Spire implements a typeclass hiearchy based on mathematical definitions (Rings, Fields, etc.)
  • Lots of good macros, lots of @inline-ing
  • Definitions for vectorized operations on arrays, etc.
  • Caveats: importing right implicits and syntax

The cfor macro

cfor(0)(_ < 10, _ + 1) { i =>
println(i)
}
def cforMacro[A](c: Context)(init: c.Expr[A])
(test: c.Expr[A => Boolean], next: c.Expr[A => A])
(body: c.Expr[A => Unit]): c.Expr[Unit] = {
import c.universe._
val util = SyntaxUtil[c.type](c)
val index = util.name("index")
val tree = if (util.isClean(test, next, body)) {
q"""
var $index = $init
while ($test($index)) {
$body($index)
$index = $next($index)
}
"""
} else {
...
}
new InlineUtil[c.type](c).inlineAndReset[Unit](tree)
}

Extending to your own types

  • Define appropriate typeclasses, Spire gives syntax etc. for free
  • Example: Define a Ring and VectorSpace for a square matrix

implicit def matrixIsRing[N <: Int with Singleton : Witness.Aux, R : Field]: Ring[Matrix[N, R]] = new Ring[Matrix[N, R]] {

  override def negate(x: Matrix[N, R]): Matrix[N, R] =
    Matrix((i, j) => -x(i, j))

  override def zero: Matrix[N, R] =
    Matrix((i, j) => Field[R].zero)

  override def plus(x: Matrix[N, R], y: Matrix[N, R]): Matrix[N, R] =
    Matrix((i, j) => x(i, j) + y(i, j))

  override def minus(x: Matrix[N, R], y: Matrix[N, R]): Matrix[N, R] =
    Matrix((i, j) => x(i, j) - y(i, j))

  override def one: Matrix[N, R] =
    Matrix((i, j) => if (i == j) Field[R].one else Field[R].zero)

  override def times(x: Matrix[N, R], y: Matrix[N, R]): Matrix[N, R] =
    Matrix((i, j) => x.rows(i) dot y.columns(j))

}

implicit def matrixIsVectorSpace[N <: Int with Singleton : Witness.Aux, R](implicit f: Field[R]): VectorSpace[Matrix[N, R], R] = new VectorSpace[Matrix[N, R], R] {

  override def scalar: Field[R] = f

  override def timesl(r: R, v: Matrix[N, R]): Matrix[N, R] =
    Matrix[N, R]((i: Int, j: Int) => r * v(i, j))

  override def divr(v: Matrix[N, R], r: R): Matrix[N, R] =
    Matrix[N, R]((i: Int, j: Int) => v(i, j) / r)

  override def negate(x: Matrix[N, R]): Matrix[N, R] =
    Ring[Matrix[N, R]].negate(x)

  override def zero: Matrix[N, R] =
    Ring[Matrix[N, R]].zero

  override def plus(x: Matrix[N, R], y: Matrix[N, R]): Matrix[N, R] =
    Ring[Matrix[N, R]].plus(x, y)

  override def minus(x: Matrix[N, R], y: Matrix[N, R]): Matrix[N, R] =
    Ring[Matrix[N, R]].minus(x, y)

}

Use case: automatic differentiation

Dual numbers

  • Define \(\epsilon\) such that \(\epsilon^2 = 0\)
  • (Similar to imaginary number \(i^2 = -1\))
  • \(z = a + b\epsilon\) is a dual number
  • Special property that \[f(x + \epsilon) = f(x) + f'(x)\epsilon\]
  • Spire implements these as Jet

AD in Spire

import spire.implicits._
import spire.algebra.Ring
import spire.math.{Jet, JetDim}
implicit val jd = JetDim(1)
def square[T : Ring](x: T) = x * x
square(Jet(2.0, 0)) // (4.0 + [4.0]h)

Concluding thoughts

  • If you use numbers or love math Spire is fun to work with
  • I think it makes great reading material
  • Use more typeclasses
  • Spire demonstrates typeclasses that are performant and practical
  • Questions?