8. Dotty is the codename for the new Scala compiler
• It has been rewritten from scratch for speed and correctness
The language has been simplified and extended for Dotty
• Unpopular and redundant features have been removed
• New features reflect the increasing importance of FP
• Implicits and macros remain, but in a new way
Enter Dotty
9. There is a new intermediate format
• Your Scala 3 code compiles into TASTy
• Short for Typed Abstract Syntax Trees
• We then generate JS, Bytecode etc..
You can reflect on TASTY Syntax Trees
• This is how macros are implemented
Enter Dotty
10. How To Try Out Scala 3
brew install sbt
brew install lampepfl/brew/dotty
Install VS Code and add to PATH
• https://code.visualstudio.com/docs/setup/mac#_command-line
sbt new lampepfl/dotty.g8
• Enter the info the wizard requires
Change directory into project folder
sbt launchIDE
12. Name Notes
XML Literals Believe it or not this was once a great idea
Delayed Init This partially breaks the ‘App’ class
Procedure Syntax Writing ‘def foo() { … }’ no longer declares a return type of Unit
The Do While Loop What?
Existential Types
Class Shadowing
Symbol Literals
Auto Application
Weak Conformance
Compound Types
Auto Tupling
[this] Qualifier
Stuff That Dies
13. def fakingDoWhile() = {
println("Output from first weird loop...")
while({
val x = Math.random()
println(s"t$x")
x > 0.5
})()
println("Output from second weird loop...")
while {
val x = Math.random()
println(s"t$x")
x > 0.5
} do ()
}
Output from first weird loop...
0.6506029560584671
0.4481933779924502
Output from second weird loop...
0.9451875748422434
0.5031857996404466
0.5923768910372654
0.8600801357886065
0.536180700764455
0.7337064700795435
0.7930386780841578
0.2882381886829134
No Do…While Loops
14. Anything can be declared at the top level
• Including Types, Variables and Functions
• You no longer need package objects
Any function can be main
• By annotating with with ‘@main’
Top Level Declarations
15. val menu = ""”
Pick an option:
1) Creator applications
2) Extension methods
3) Basic enumerations
4) Enumerations as ADTs
"""
@main def foobar(): Unit = {
var notDone = true
while(notDone) {
println(menu)
val choice = scala.io.StdIn.readInt()
choice match {
case 1 => creatorApplications()
case 2 => extensionMethods()
case _ => notDone = false
}
}
}
Pick an option:
1) Creator applications
2) Extension methods
3) Basic enumerations
4) Enumerations as ADTs
1
Jane aged 25
Dave aged 27
Top Level Declarations
16. package foo
def *() = println("Hello dere...")
Hello dere...
A Fun Obfuscation
package bar
import foo.*
@main def bar() = *()
17. You could always avoid ‘new’ in Scala via an ‘apply’ method
• But it would be laborious to do this for every type in your model
Scala 3 makes it trivial to avoid new in almost all cases
This is done by adding a fourth way of interpreting a function:
This is referred to as ‘Creator Applications’
Creator Applications
[Given f(args)] if f is syntactically a stable
identifier, and new f where f is interpreted
as a type identifier is applicable to args,
continue with new f(args)
18. class Person(name: String, age: Int) {
def this(name: String) = this(name, 27)
override def toString() = s"$name aged $age"
}
def creatorApplications() = {
val p1 = Person("Jane", 25)
val p2 = Person("Dave")
println(p1)
println(p2)
}
Jane aged 25
Dave aged 27
Creator Applications
19. Scala 3 supports extensions to existing types
• Useful when you don’t have the source or don’t want to modify it
Extension methods can be defined singly or in groups
• The latter is referred to as a collective extension
Extensions can be operators and can be applied to generic types
Extension Methods
20. def (s: String).times2() = s + s
extension on (s: String) {
def times3() = s + s + s
def times4() = s + s + s + s
}
def extensionMethods() = {
val s1 = "Wibble"
println(s1.times2())
println(s1.times3())
println(s1.times4())
}
WibbleWibble
WibbleWibbleWibble
WibbleWibbleWibbleWibble
Extension Methods
21. Scala finally does Enums!
• These were not available previously
• The idea was to use Case Classes
You can extend ‘java.lang.Enum’
• For interoperability with Java
Enums can have type parameters
• Giving you an easy way to do ADTs
Enumerations J
22. enum Direction {
case NORTH, SOUTH, EAST, WEST
def printArrow() = {
var arrow = '?’
this match {
case WEST => arrow = 0x2190
case EAST => arrow = 0x2192
case NORTH => arrow = 0x2191
case SOUTH => arrow = 0x2193
}
println(arrow)
}
}
def basicEnumerations() = {
val d = Direction.NORTH
print(d)
d.printArrow()
}
NORTH↑
Enumerations J
25. Scala Verses Kotlin
“There’s something I ought to tell you”
“Tell me”
“I don’t use the new keyword either”
“I don’t use implicits either”
“I also have enumerated types”
“I also have extension methods”
“I can declare top level types”
Vs
26. The new enumeration syntax makes it easy to create ADT’s
• As in particular generic ADT’s like Option, Try and Either
You could always do this with Case Classes
• But the process is simplified to reflect their increasing relevance
Enumerations as ADTs
27. enum Try[+T] {
case Success(val result: T) extends Try[T]
case Failure(val error: String) extends Try[Nothing]
def fold(errorHandler: String => Unit,
resultHandler: T => Unit) = {
this match {
case Success(result) => resultHandler(result)
case Failure(error) => errorHandler(error)
}
}
}
def readPropertyAsInt(name: String): Try[Int] = {
val propertyValue = System.getProperty(name)
try {
Try.Success(Integer.parseInt(propertyValue))
} catch {
case ex: NumberFormatException => Try.Failure(ex.getMessage)
}
}
Enumerations as ADTs
28. def enumerationsAsADTs() = {
System.setProperty("testProp", "123")
val result1 = readPropertyAsInt("testProp")
val result2 = readPropertyAsInt("java.vendor")
result1.fold(
msg => println(s"Whoops - $msg"),
num => println(s"Hurrah - $num")
)
result2.fold(
msg => println(s"Whoops - $msg"),
num => println(s"Hurrah - $num")
)
}
Hurrah - 123
Whoops - For input string: "AdoptOpenJDK"
Enumerations as ADTs
29. Scala 3 adds support for:
• Union Types
• Intersection Types
• Literal Types
• Opaque Type Aliases
This gives a richer toolkit for describing data
• Fans of DDD should be very happy
New Ways To Manipulate Types
30. Parameters in Scala 3 can be defined as unions of types
• Where the types don’t have to share a common base class
For example ‘def foo(input: Foo | Bar | Zed)’
This will also work with literals
Union Types
31. case class WorkExperience(val years: Int)
case class Degree(val subject: String)
case class Publication(val isbn: String)
def employ(name: String, reason: Degree | WorkExperience | Publication) = {
print(s"Employing $name because of ")
reason match {
case Degree(subject) => println(s"their degree in $subject")
case WorkExperience(years) => println(s"their $years years experience")
case Publication(_) => println("they wrote a book")
}
}
Union Types
33. Parameters can also be the intersection of multiple types
• Where a type must extend multiple traits
For example ‘def foo(input: Foo & Bar & Zed)’
Intersection Types
34. trait Edible {
def eat(): Unit
}
trait Drinkable {
def drink(): Unit
}
class Apple extends Edible {
override def eat() = println("Eating an Apple")
}
class Milk extends Drinkable {
override def drink() = println("Drinking some Milk")
}
class Soup extends Edible with Drinkable {
override def eat() = println("Eating Soup")
override def drink() = println("Drinking Soup")
}
Intersection Types
36. Literal Types started in Dotty but were backported to 2.13
• They allow you to treat any literal value as a type in its own right
• 1, 2.3, “ABC” can all be used as types with a singleton instance
These are especially powerful when combined with Union Types
• E.g. the input to a method can be constrained to the names of events
Literal Types
38. Opaque type aliases provide abstraction without overhead
Let’s say you want to have an ‘ItemID’ type
• Which can be expressed as a regular String
You are concerned about erroneous conversions
• E.g. ‘addItem(“Toaster”)’ instead of ‘addItem(“AB12”)’
One option would be to create a wrapper type
• An ‘ItemID’ class with a single field of type String
• But this would introduce the overhead of (un)boxing
Type aliases provide a neat solution
Opaque Type Aliases
43. Scala Verses F#
“There’s something I ought to tell you”
“Tell me”
“I don’t need wrapper types either”
“I also support union types”
Vs
44. Significant Whitespace
This one was a shock
• …and it is still taking a while to percolate
Scala 3 supports significant whitespace
• If you omit the parenthesis at the start of a
function then whitespace is used instead
• All the conditionals and loops have a new
form that does not need parenthesis
45. @main def foobar(): Unit =
var notDone = true
while notDone do
println(menu)
val choice = scala.io.StdIn.readInt()
choice match
case 1 => creatorApplications()
case 2 => extensionMethods()
case _ => notDone = false
Significant Whitespace
@main def foobar(): Unit = {
var notDone = true
while(notDone) {
println(menu)
val choice = scala.io.StdIn.readInt()
choice match {
case 1 => creatorApplications()
case 2 => extensionMethods()
case _ => notDone = false
}
}
}
48. Collections have always been a strong point for Scala
• Despite complaints that they are too pure or should be purer J
There were improvements in 2.13 which are continued into to 3.0
• New methods have been added and the implementation is simplified
Improved Collections
49. enum Rating {
case GREAT, BRILLIANT, SUPERB, LIFE_CHANGING
}
class Movie(val title: String,
val rating: Rating,
val releaseDate: LocalDate,
val quotes: List[String])
def greatActionMovies(): List[Movie] = List(
Movie("Predator",
Rating.LIFE_CHANGING,
of(1987, JUNE, 12),
List("Get to the Chopper!",
"Stick around.",
"If it bleeds, we can kill it.",
"He's using the trees.",
"We move, five meter spread, no sound.")),
Movie(...),
Improved Collections
50. def triplesOfQuotes(title: String) = {
val movies = greatActionMovies()
println(s"All possible triples of $title quotes")
movies.find(_.title == title)
.map(m => m.quotes.combinations(3))
.fold(println("Not found!"))(_.foreach(println))
}
triplesOfQuotes("Commando")
All possible triples of Commando quotes
List(I have to remind you Sully, this is my weak arm!,
I eat Green Berets for breakfast. And right now, I'm very hungry!,
Don't disturb my friend, he's dead tired.)
List(...)
List(...)
List(...)
List(...)
Improved Collections
51. def movieTitlesByMonthReleased() = {
val movies = greatActionMovies()
println("Movie titles grouped by month of release")
movies.groupMap(_.releaseDate.getMonth)(_.title)
.foreach(row => {
val(month, titles) = row
print(s"tMovies released in $month: ")
titles.foreach(t => print(s" '$t'"))
println()
})
}
Movie titles grouped by month of release
Movies released in JUNE: 'Conan' 'Predator' 'Total Recall’
Movies released in OCTOBER: 'Terminator' 'Commando’
Movies released in JULY: 'Terminator 2'
Improved Collections
52. Inheritance is frequently overused in OO
• In particular the anti-pattern of ‘implementation inheritance’
• Were we inherit from a type solely to reuse its functionality
Export Clauses provide a much better alternative
• They make it easy for one type to wrap up another
• Whilst selectively exposing its functionality
Export Clauses
53. class MyBuffer {
val sb = new StringBuffer()
export sb.append
export sb.{ insert => putIn }
override def toString() = sb.toString()
}
def exportClauses() = {
val buffer = new MyBuffer()
buffer.append("Foo")
buffer.append("Bar")
buffer.append("Zed")
buffer.putIn(3, "Wibble")
buffer.putIn(12,"Wobble")
println(buffer)
}
FooWibbleBarWobbleZed
Export Clauses
54. Scala 3 allows Traits to have parameters
Trait Parameters
55. trait PricingEngine(val minPurchases: Int) {
def calcDiscount(purchases: List[String]) : Double
}
class MyPricingEngine extends PricingEngine(3) {
override def calcDiscount(purchases: List[String]) = {
if purchases.length >= minPurchases then 100.0 else 0.0
}
}
def traitParameters() = {
val pe = new MyPricingEngine()
println(pe.calcDiscount(List("AB12","CD34")))
println(pe.calcDiscount(List("AB12","CD34","EF56")))
}
0.0
100.0
Trait Parameters
56. Scala’s implicits let you abstract over context
• They are a great way to perform DI and implement Type Classes
However when incorrectly used they are a ‘chainsaw feature’
• You lose control over what the compiler is injecting on your behalf
In version 3 implicits are replaced with:
• Given instances
• Using classes
• Given imports
• Implicit conversions
Implicits Are Replaced
57. trait MakeImportant[T] {
def escalate(thing: T): T
}
given stringMakeImportant as MakeImportant[String] {
def escalate(thing: String) = thing.toUpperCase()
}
given intMakeImportant as MakeImportant[Int] {
def escalate(thing: Int) = thing * 100
}
Implicits Are Replaced
58. def doImportantWork[T](thing: T)(using imp: MakeImportant[T]) = {
print("Doing important work with: ")
println(imp.escalate(thing))
}
def givenInstances() = {
doImportantWork("wibble")
doImportantWork(123)
}
Doing important work with: WIBBLE
Doing important work with: 12300
Implicits Are Replaced
59. Scala 3 provides a new library for metaprogramming
• Replacing earlier attempts to add macros to the language
An inline keyword expands function calls at the point of use
• Unlike other languages the instruction to inline is a command
Quotation converts code into typed syntax trees
• Using a dedicated API and the new TASTy representation
• The syntax is ‘{…} for expressions and ‘[…] for types
Spicing converts an AST back into code
• The syntax is ${…}
Metaprogramming