ABSTRACTIONS IN DOTTY

FOR FUN AND PROFIT

Felix Mulder

Slides: felixmulder.com

Dotty

  • A proven foundation - DOT
  • Simplification
  • Compilation Speed
  • Library defined rewrites
  • Developer Usability
  • Scala 3

“Release Manager”

ERRGONOMICS

  • Awesome Error Messages
  • Dottydoc
  • REPL
  • Lots of fixes
def spiritOfDoing(you: Programmer, client: DomainExpert): Task[Joy] =
  for {
    domain     <- problemDescription(client)
    goals      <- you.understand(domain)
    solution   <- goals.attempt
    conclusion <- conclusion match {
      case client.Satisfied(conclusion) => solution.pure[Task] // Done!
      case lessonsLearned => spiritOfDoing(you + lessonsLearned,
                                           client + lessonsLearned)
    }
  } yield conclusion
def spiritOfWhatAmIDoing(you: Programmer,
                         client: DomainExpert): Task[Option[Joy]] =
  for {
    domain     <- problemDescription(client)
    _          =  complainAboutThings(domain)
    goals      <- you.understand(domain)
    _          =  complainAboutThings(client)
    solution   <- goals.attempt
    _          =  complainAboutThings(solution)
    conclusion <- conclusion match {
      case client.Satisfied(conclusion) =>
        complainAboutThings(BeingDoneFinally())
        solution.pure[Task] // Done!
      case ex @ you.RageQuit => Task.fail(ex)
      case lessonsLearned =>
        bitchAndMoan()
        spiritOfDoing(you + lessonsLearned + you.pentUpAggression(solution),
                      client + lessonsLearned)
    }
  } yield conclusion

Anyway…Dotty!

Key Differences

  • Explicit Implicits
  • No more procedure syntax
  • Union Types
  • Intersection Types
  • Trait Parameters
  • Implicit Functions
  • Enums
  • Callgraph, automatic specialization
  • Library defined rewrites
  • TASTY
  • IDE using Visual Studio Code

Key Differences

  • Explicit Implicits
  • No more procedure syntax
  • Union Types
  • Intersection Types
  • Trait Parameters
  • Implicit Functions
  • Enums
  • Callgraph, automatic specialization
  • Library defined rewrites
  • TASTY
  • IDE using Visual Studio Code

A Trip Down Memory Lane

// 1 + 2 - 3 => Add(Lit(1), Add(Lit(2), Neg(Lit(3))))
trait Exp[T]
class Lit[T](t: T) extends Exp[T]
class Neg[T](t: T) extends Exp[T]
class Add[T](t1: T, t2: T)
trait Exp[T]
case class Lit[T](t: T) extends Exp[T]
case class Neg[T](t: T) extends Exp[T]
case class Add[T](t1: T, t2: T) extends Exp[T]
sealed trait Exp[T]
final case class Lit[T](t: T) extends Exp[T]
final case class Neg[T](t: T) extends Exp[T]
final case class Add[T](t1: T, t2: T) extends Exp[T]

ENUMS

enum Color {
  case Red
  case Green
  case Blue
}
enum Color { case Red, Green, Blue }
sealed trait Exp[T]
final case class Lit[T](t: T) extends Exp[T]
final case class Neg[T](t: T) extends Exp[T]
final case class Add[T](t1: T, t2: T) extends Exp[T]
enum Exp[T] {
  case Lit(t: T)
  case Add(t1: T, t2: T)
  case Neg(t: T)
}
enum Option[+T] {
  case Some(t: T) // extends Option[T]
  case None       // extends Option[Nothing]
}
enum Option[+T] extends Serializable {
  case Some(t: T)
  case None
}
enum class Option[+T] extends Serializable

object Option {
  case Some(t: T)
  case None

  def apply[T](t: T): Option[T] =
    if (t != null) Some(t) else None
}

Enum Applys

val x = Some(1)     // x: Option[Int]
val y = new Some(1) // y: Option.Some[Int]

Waddler’s Law

In any language design, the total time spent discussing a feature in this list is proportional to two raised to the power of its position.

  • 0. Semantics
  • 1. Syntax
  • 2. Lexical syntax
  • 3. Lexical syntax of comments

Implement a proposal!

Implement a proposal!

Paramater Dependent Types,

and why we use Aux

IS WAT?

def foo[A <: AnyRef](a: A): a.type = a
trait Foo[A] { type X }

def bar[A <: AnyRef](a1: A)
                    (implicit a2: Foo[a1.type]): a2.X = a
trait Foo[A] { type X }

def bar[A <: AnyRef, B](a1: A)
                       (implicit a2: Foo[a1.type]{ type X = B },
                                 a3: Foo[B]): a3.X = a
trait Foo[A] { type X }

def bar[A <: AnyRef, B, C](a1: A)
                          (implicit a2: Foo[a1.type]{ type X = B },
                                    a3: Foo[B      ]{ type X = C },
                                    a3: Foo[C      ]): a3.X = a
trait Foo[A] { type X }

def bar[A <: AnyRef, B, C, D](a1: A)
                             (implicit a2: Foo[a1.type]{ type X = B },
                                       a3: Foo[B      ]{ type X = C },
                                       a4: Foo[C      ]{ type X = D },
                                       a5: Foo[D      ]): a5.X = a
trait Foo[A] { type X }

type Aux[A, B0] = Foo[A] { type X = B0 }

def bar[A <: AnyRef, B](a1: A)
                       (implicit a2: Aux[a1.type, B],
                                 a3: Foo[B]): a3.X = a
trait Foo[A] { type X }

type Aux[A, B0] = Foo[A] { type X = B0 }

def bar[A <: AnyRef, B](a1: A)
                       (implicit a2: Aux[a1.type, B],
                                 a3: Foo[B]): a3.X = a

In Dotty:

def bar[A <: AnyRef](a1: A)
                    (implicit a2: Foo[a1.type],
                              a3: Foo[a2.X]): a3.X = a

TYPE INFERENCE

Dotty and Types: The Story So Far

by Guillaume Martres

trait List[+A] {
  def foldRight[B](z: B)(f: (A, B) => B): B = ???
}

List(1, 2, 3).foldRight(List.empty)(_ :: _) // scalac: nope 
                                            //  dotty:  yep 

Union types and intersection types

Can we ditch coproducts in favor of or-types?

Implicit Functions

implicit A => B
def saveUser(u: User)(implicit dbx: DBContext): DBIO[User] = {
  ...
}
def saveUser(u: User): DBIO[User] = { dbx =>
  ...
}
def saveUser(u: User): DBIO[User] = { implicit dbx =>
  ...
}
type DBIO[T] = implicit DBContext => T

def saveUser(u: User): DBIO[User] = {
  // here we have a DBContext implicitly available, yay!
  ...
}
def saveUser(u: User): DBIO[User] =
  implicit ctx => u

Tagless Final Interpreters

trait Exp[T] {
  def add(t1: T, t2: T): T
  def lit(i: Int) = T
}

implicit val intExp: Exp[Int] = new Exp[Int] {
  def add(t1: Int, t2: Int) = t1 + t2
  def lit(i: Int) = i
}

// 8 + (2 + 2 + 2)
def expr1[T](implicit e: Exp[T]) =
  e.add(e.lit(8), e.add(e.lit(2), e.add(e.lit(2), e.lit(2))))
object ExpSyntax {
  def lit[T](i: Int)(implicit e: Exp[T]): T = e.lit(i)
  def add[T](l: T, r: T)(implicit e: Exp[T]): T = e.add(l, r)
}
import ExpSyntax._
trait Exp[T] {
  def add(t1: T, t2: T): T
  def lit(i: Int) = T
}

implicit val intExp: Exp[Int] = new Exp[Int] {
  def add(t1: Int, t2: Int) = t1 + t2
  def lit(i: Int) = i
}

// 8 + (2 + 2 + 2)
def expr1[T: Exp] =
  add(lit(8), add(lit(2), add(lit(2), lit(2))))
trait Mul[T] {
  def mul(t1: T, t2: T): T
}

implicit val intMul: Mul[Int] = new Mul[Int] {
  def mul(i1: Int, i2: Int) = i1 * i2
}

// 8 + 3 * 2
def expr2[T : Exp : Mul] =
  add(lit(8), mul(lit(3), lit(2)))
type Ring[T] = implicit (Exp[T], Mul[T]) => T

// 8 + 3 * 2
def expr2: Ring[T] =
  add(lit(8), mul(lit(3), lit(2)))
Revisiting Tagless Final Interpreters - Olivier Blanvillain
  • Dottydoc

    • Markdown
    • Jekyll-like static site generation
    • Cross referencing
    • Compiled examples à la tut
  • Awesome Error Messages

    • Presentation
    • Semantic info
    • Detailed explanations
  • TBA REPL

  • VSC Language Server

Awesome Error Messages

REPL

Issues

ERROR MESSAGES

Issue #1589

IDEs & TOOLING

Issues

Build Tools

Answers

  • 0.x release - ScalaDays CPH
  • SI-2712? Fixed two years ago
  • Limit 22? No.
  • Tuples? No, HLists.
  • Does it run my project? Try it: sbt-dotty
  • I have a suggestion!
  • What about feature X?

Question?

Thank you!