“If” statements at compile time

Justin Luebke on unspash

“If” statements at run-time

def sqrt(x: Double): Double =
if x < 0 then throw new IllegalArgumentException("can't take square root of negative numbers")
else math.sqrt(x)

“If” statements at compile time

trait RuntimeList[+A]:
def toMap[K, V]: Map[K, V]
class NonEmptyRuntimeList[+A](head: A, tail: Run-timeList[A]) extends RuntimeList[A]:
def toMap[K, V]: Map[K, V] = (head match {
case h: (K, V) => Map(h._1 -> h._2)
case _ => throw new RuntimeException("elements of the list must be pairs!")
}) ++ tail.toMap[K, V]
object EmptyRuntimeList extends RuntimeList[Nothing]:
def toMap[K, V]: Map[K, V] = Map.empty
@ new NonEmptyRuntimeList(3, EmptyRuntimeList).toMap[Int, String]
java.lang.RuntimeException: elements of the list must be pairs!
@ new NonEmptyRuntimeList((3, "hello"), EmptyRuntimeList).toMap[Int, String]
res2: Map[Int, String] = Map(3 -> "hello")
def toMap[K, V](using ev: A <:< (K, V)): Map[K, V]
[imaginary language]
if A is a pair then "let the thing compile"
else "crash, aka, compile error"
  • variables at run-time correspond to types at compile time
  • “if” statements about the run-time values corresponds to asking for given values of special types representing types equality or type inheritance relations at compile type

An equality check

def print()(using ev: Unit =:= A): String = print(ev(()))
def printAsPair[K, V](a: A)(using ev: A =:= (K, V)): String =
val (key, value) = ev(a)
s"$key: $value"
def print()(using A =:= Nothing): String = "Nothing there!"

What about “not equal”?

[imaginary language]
if A != Foo then "let it go"
else "crash, aka don't compile"
type IsNotNothing[A] <: Boolean = A match {
case Nothing => false
case _ => true
}
trait IO[E, A]:
// elided content ...
def mapError[F](f: E => F)(using IsNotNothing[E] =:= true): IO[F, A] = ??? // do your thing
val ioNothing: IO[Nothing, Int] = ???
ioNothing.mapError(_ => 2)
// [error] 18 | ioNothing.mapError(_ => 2)
// [error] | ^
// [error] |Cannot prove that IsNotNothing[Nothing] =:= (true : Boolean).

Other alternatives

  • Asking a function argument: An easy way, that you can actually do in any language, would be to simply define functions with the constraints that you want. For example, in order to turn a List into a Map, you can do def listToMap[K, V](ls: List[(K, V)]): Map[K, V]
  • Using extension method: In Scala, you have the ability to do type safe monkey patching by adding methods to elements after their definition. Imagining that the `toMap` method on Lists does not exist, you could add it with
extension [K, V](ls: List[(K, V)])
def toMap: Map[K, V] = ???
  • you don’t have access to private members of the elements you manipulate
  • your IDE will have a hard time helping you discover these methods. In the end, a language must help the developper be more productive, and learn faster. With the evidence pattern, your IDE will always show you that the method exists when browsing methods from the variable you manipulate (even if the evidence can’t be provided by the compiler)
  • if you generate Scala doc for your classes, they wont be where they belong: alongside your class definitions

Related works

Typeclass derivations

Counting at compile time

Derivatives at compile time

Closing words

--

--

--

Mathematician, Scala enthusiast, father of two.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Why We Use Erlang/OTP in æternity

4 Steps to Tackle Digital Transformation Head-on

LUFS & Loudness…why they matter

Azure Kubernetes Service (AKS)

The incredible FISR algorithm and how it works

Which library to generate Excel in C#? OpenXmlSdk or ClosedXml?

What is Hexagonal Architecture

Make Python Objects Magical

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Antoine Doeraene

Antoine Doeraene

Mathematician, Scala enthusiast, father of two.

More from Medium

Implementing a Clean Architecture Application in Scala — Part 1

Our Clean Architecture

val bottle and var well (Scala)

Reactive HTTP streams in Akka and the Scala Standard Library w/ DB2

Demystifying concurrency using Actors, Let there be Abstraction (Part 1)