Expression => Result
IO[String] => Result[String]
type Result[T] = ???
type Result[T] = ValidatedNel[ErrorMessage, T]
*that aren’t Java
type Result[T] = List[ErrorMessage] | T
type Result[T] = scala.util.Either[List[ErrorMessage], T]
val readLine: IO[String] = IO { "val x = 5" }
def interpret(input: String): Result[String] = ...
val pipeline: IO[String] = for {
input <- readLine
result = interpret(input).fold(reportErrors, reportResult)
} yield result
pipeline.unsafeRunSync() // "val x: String = 5"
def interpret(input: String): Result[String] =
compile(input).flatMap(evaluate)
def compile(input: String): Result[tpd.Tree] =
for {
exprs <- parse(input)
contained <- wrap(exprs)
typed <- compile(contained)
} yield typed
def evaluate(tree: tpd.Tree): Result[String] = ...
scala> val x = 5
// =>
object rs$l1 {
val x = 5
}
scala> 5
// =>
object rs$l2 {
val res0 = 5
}
scala> class Foo
// =>
object rs$l3 {
class Foo
}
scala> val y = x
// =>
object rs$l4 {
import rs$l1._
val y = x // error: not found: value x
}
val y = x
// =>
object rs$l4 {
import rs$l1._, rs$l2._, rs$l3._
val y = x
}
scala> implicit val x: String = "wrong"
scala> implicit val y: String = "right"
scala> implicitly[String]
// =>
object rs$l1 { implicit val x: String = "wrong" }
object rs$l2 {
import rs$l1._
implicit val y: String = "right"
}
object rs$l3 {
import rs$l1._, rs$l2._
val res0 = implicitly[String] // error: ambiguous implicit values
}
🙈
Context
is analogous to scalac’s Global
package example
class A
class B {
def f = ???
def g = ???
}
package example {
// Context(owner = example, scope = Scope(A, B))
class A
class B {
// Context(owner = B, scope = Scope(A, B, f, g))
def f = ???
def g = ???
}
}
object rs$l1 { implicit val x: String = "wrong" }
object rs$l2 {
// Context(owner = rs$l2, scope = ShadowScope(rs$l1._))
import rs$l1._
implicit val y: String = "right"
}
object rs$l3 {
// Context(owner = rs$l2, scope = ShadowScope(rs$l2._, rs$l1._))
import rs$l1._, rs$l2._
val res0 = implicitly[String] // val res0: String = "right" 😁👍
}
def compile(input: String): Result[tpd.Tree] =
for {
exprs <- parse(input)
contained <- wrap(exprs)
typed <- compile(contained)
} yield typed
def compile(tree: untpd.Tree)(implicit ctx: Context): tpd.Tree = {
def addMagicImports(initCtx: Context): Context =
(initCtx /: lineNumbers) { (ctx, line) =>
ctx.setNewScope.setImportInfo(importRef(line))
}
// Use dotty internals to compile the `tree` => then
// return the typed version of it
}
def interpret(input: String): Result[String] =
compile(input).flatMap(evaluate)
def evaluate(tree: tpd.Tree): Result[String] = {
// 1. Render definitions: class, trait, object, type, def
// 2. Render values: val, var
}
def evaluate(tree: tpd.Tree): Result[String] = {
val defs = ctx.atPhase(ctx.typerPhase.next) {
tree.symbol
.find(isWrapper).toList
.flatMap(collectDefs)
.map(renderDefs)
}
val values = renderValues(tree)
// Return everything separated by newlines:
(defs ++ values).mkString("\n")
}
“Let’s just use Reflection” - Java devs
def renderValues(tree: tpd.Tree): List[String] = {
def valueOf(sym: Symbol): Option[String] = { ... }
collectValues(tree).map { symbol =>
val dcl = symbol.showUser
val res = if (symbol.is(Lazy)) Some("<lazy>") else valueOf(symbol)
res.map(value => show"$dcl = $value")
}
}
def valueOf(sym: Symbol): Option[String] = {
val wrapperName = sym.owner.name
val wrapper = Class.forName(wrapperName, true, classLoader)
val res =
wrapper
.getDeclaredMethods.find(_.getName == sym.name + "Show")
.map(_.invoke(null).toString)
if (!sym.is(Flags.Method) && sym.info == defn.UnitType)
None
else res
}
trait Show[-T] {
def show(t: T): String
}
scala> val x = 5
// =>
object rs$l1 {
val x = 5
def xShow = x.show
}
Trivia - why a def
?
def valueOf(sym: Symbol): Option[String] = {
val wrapperName = sym.owner.name
val wrapper = Class.forName(wrapperName, true, classLoader)
val res =
wrapper
.getDeclaredMethods.find(_.getName == sym.name + "Show")
.map(_.invoke(null).toString) // Initializes the object 🙊
if (!sym.is(Flags.Method) && sym.info == defn.UnitType)
None
else res
}
Expression => Result
🎩 + 🐿 == `Ship it!`
type Result[T] = List[ErrorMessage] | T
type Result[T] = List[ErrorMessage] | T
implicit class ResultOps[A](res: Result[A]) extends AnyVal {
def flatMap[B](f: A => Result[B]): Result[B] = res match {
case err: List[ErrorMessage] => err // warning: type erasure
case a: A => f(a) // warning: match on generic type
}
}
type Result[T] = Errors | T
private case class Errors(values: List[ErrorMessage])
implicit class ResultOps[A](res: Result[A]) extends AnyVal {
def flatMap[B](f: A => Result[B]): Result[B] = res match {
case err: Errors => err
case a: A @unchecked => f(a)
}
...
}
for { x <- 1 } yield 1
🙈
1 <:< Result[T]
1 <:< (Errors | T)
(1 <:< Errors) || (1 <:< T)
false || (1 <:< T)
true
Either
Coproduct
enum Message {
case PlainMessage[A](value: A)
case ComputableMessage[A](value: () => A)
case NoMessage
}
type SomeMessage[A] = PlainMessage[A] | ComputableMessage[A]
def log[A : Broker](msg: SomeMessage): IO[Unit] =
msg match {
case msg: PlainMessage => IO { println(msg) }
case msg: ComputableMessage => IO { println(msg.compute) }
}
(String | Int) =:= (Int | String)
enum Days {
case Monday
case Tuesday
case Wednesday
case Thursday
case Friday
case Saturday
case Sunday
}
type Weekday = Monday.type | Tuesday.type | ... | Friday.type
type Weekend = Saturday.type | Sunday.type
type UndefOr[A] = A | Unit
val res: Either[Exception, Int] = Right(1)
res.map(_ + 1)
val res: Either[Exception, (Int, String)] = Right((1, "foo"))
res.map((i, str) => (i + 1, str + "bar"))
// error: found (Int, String) => (Int, String),
// required ((Int, String)) => ?
res.map { case (i, str) => (i + 1, str + "bar") }
implicit class ListOps[A](val xs: List[A]) extends AnyVal {
def myFoldLeft1[B](init: B, f: (B, A) => B): B = ...
def myFoldLeft2[B](init: B)(f: (B, A) => B): B = ...
}
List(1, 2, 3).myFoldLeft2(0)(_ + _)
List(1, 2, 3).myFoldLeft1(0, _ + _)
// error: missing parameter type for expanded function
// ((x$1: , x$2: Int) => x$1.$plus(x$2))
List(1, 2, 3).myFoldLeft1(0, (a: Int, x: Int) => a + x)
List(1, 2, 3).myFoldLeft1[Int](0, _ + _)
xs.foldRight(List.empty)(_ :: _)
// res0: List[Any](1, 2, 3)
def foo: List[Int] = xs.foldRight(List.empty)(_ :: _)
🤗
trait Foo[A] { type B }
def foo[A](t: A)
(implicit f: Foo[A], m: Monoid[f.B]): f.B = m.zero
trait Show[-T] {
def apply(t: T): String
}
trait Show[-T] {
def apply(t: T): String
}
class A
class B extends A
class C extends B
implicit val showAny = new Show[Any] { def apply(any: Any) = "showing Any" }
implicit val showA = new Show[A] { def apply(a: A) = "showing A" }
implicit val showB = new Show[B] { def apply(b: B) = "showing B" }
implicit val showC = new Show[C] { def apply(c: C) = "showing C" }
implicitly[Show[C]].apply(new C) // res: "showing Any"
trait Show[-T] {
def apply(t: T): String
}
class A
class B extends A
class C extends B
implicit val showAny: Show[Any] = new { def apply(any: Any) = "showing Any" }
implicit val showA: Show[A] = new { def apply(a: A) = "showing A" }
implicit val showB: Show[B] = new { def apply(b: B) = "showing B" }
implicit val showC: Show[C] = new { def apply(c: C) = "showing C" }
implicitly[Show[C]].apply(new C) // res: "showing C"
$ sbt new lampepfl/dotty.g8
$ brew install lampepfl/brew/dotty