Распаковка Scala 3
Олег Нижников
О Scala 3
История
Unsoundness
Unsoundness
Unsoundness
A <: M
A extends M
M <: B
M extends B
A <: B
A extends B
class Evidence[A, B <: A, C <: B]
val evidence : Evidence[Integer, _, String] = null
Unsoundness
DOT
Dotty
Changes
0.22-RC1
0.23-RC1
Roadmaps
Использование
IDE
DottyIDE (LSP)
- подсказки
- переименование
- ошибки
Metals (LSP)
- подсказки
- переименование (?)
- ошибки
- автоимпорты
- навигация в scala
- навигация в java(?)
- имплементация(?)
- запуск \ тесты
IDEA
- подсказки (?)
- переименование (?)
- ошибки (?)
- автоимпорты (?)
- навигация в scala (?)
- навигация в java(?)
- имплементация (?)
- запуск \ тесты
- стат. анализ
5 / 10
Сборка
Scala 2
- SBT
- Gradle
- Maven
- Bazel
- Pants
Scala 3
- SBT
8 / 10
Скорость компиляции
Scala 2
Scala 3
5 / 10
Очень медленно
Очень медленно
Библиотеки / Совместимость
Scala 2
Scala 3
8 / 10
6 / 10
Кроме макросов
Кроме инлайнов, экстеншенов,
и половины типов
Межверсионная совместимость
Scala 2
патч версии совместимы
2.13.1 -> 2.13.4
.scala -> .class
Scala 3
минорные версии совместимы
3.0.1 -> 3.2.4
.scala -> .tasty -> .class
10 / 10
Синтаксис
Значимые пробелы
trait A:
def f: Int
class C(x: Int) extends A:
def f = x
object O:
def f = 3
enum Color:
case Red, Green, Blue
new A:
def f = 3
package p:
def a = 1
package q:
def b = 2
def foo =
val x = 1
val y = 2
x + y
one match
case "one" => 1
case "two" => 2
if x < 0 then
"negative"
else if x == 0 then
"zero"
else
"positive"
while x < 10 do
s += x
x += 1
for
x <- xs
y <- ys
do
println(x + y)
for x <- xs if x > 0
y <- foo(x)
yield x * y
Значимые пробелы
trait A:
def f: Int
end A
class C(x: Int) extends A:
def f = x
end C
object O:
def f = 3
end O
enum Color:
case Red, Green, Blue
end Color
new A:
def f = 3
end new
package p:
def a = 1
end p
def foo =
val x = 1
val y = 2
x + y
end foo
one match
case "one" => 1
case "two" => 2
end match
if x < 0 then
"negative"
else if x == 0 then
"zero"
else
"positive"
end if
while x < 10 do
s += x
x += 1
end while
for
x <- xs
y <- ys
do
println(x + y)
end for
for x <- xs if x > 0
y <- foo(x)
yield x * y
end for
Значимые пробелы
trait A :
def f: Int =
val x = 1
x + 2
trait B extends A{
override def f = {
super.f - 1
}
}
6 / 10
Toplevel определения
package object math extends MathFunctions{
val E = 2.7182818284590452353602
def twice(x: Double) = x * x
extension (a: Double)
def log(b: Double): Double =
log(a) / log(b)
type Rational = Ratio[BigInt]
}
Scala 2
package math
val E = 2.7182818284590452353602
def twice(x: Double) = x * x
extension (a: Double)
def log(b: Double): Double =
log(a) / log(b)
type Rational = Ratio[BigInt]
Scala 3
8/ 10
Остальной синтаксис
import lol.*
@main def app =
println("Hello world")
import lol._
object app{
def main(args: Array[String]) =
println("Hello world")
}
Вместо
ООП
export
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
Kotlin Delegation
trait Base:
def print(): Unit
class BaseImpl(val x: Int) extends Base:
def print() = println(x)
class Derived(x: Base) extends Base:
export x.*
Kotlin Delegation
Scala Export
export
interface Base {
fun name(): String
fun age(): Int
}
class Derived(b1: Base, val b2: Base): Base by b1 {
override fun age(): Int {
return b2.age()
}
}
Kotlin Delegation
trait Base:
def name: String
def age: Int
class Derived(b1: Base, b2: Base) extends Base:
export b1.name
export b2.age
Kotlin Delegation
Scala Export
8 / 10
trait parameters
trait Greeting(val name: String):
def msg = s"How are you, $name"
class C extends Greeting("Bob"):
println(msg)
Early initialization
trait Foo{
val x: String
}
trait Bar extends Foo {
val y = x.toUpperCase
}
new Bar{ val x = "lol" }.y /// NPE
(new {val x = "lol"} with Bar).y
trait Foo(val x: String)
trait Bar extends Foo:
val y = x.toUpperCase
(new Bar with Foo("lol"){}).y
9 / 10
extensions
extension (x: Long) def bytes: Array[Byte]=
Array((x >> 24).toByte,
(x >> 16).toByte,
(x >> 8).toByte,
(x % 256).toByte)
Сейчас
implicit class LongOps(private val x: Long) extends AnyVal{
def bytes: Array[Byte] = Array(
(x >> 24).toByte,
(x >> 16).toByte,
(x >> 8).toByte,
(x &%256).toByte
)
}
Раньше
fun Long.toBytes(): Array<Byte>{
return arrayOf(
this.shr(24).toByte(),
this.shr(16).toByte(),
this.shr(8).toByte(),
(this % 256).toByte()
)
}
Kotlin
extensions
trait ToBytes[A]:
extension(a: A) def bytes: Array[Byte]
Сейчас
trait ToBytes[A]{
def bytes(a: A): Array[Byte]
}
implicit class ToBytesOps[A](private val a: A) extends AnyVal{
def bytes(implicit tb: ToBytes[A]): Array[Byte] = tb.bytes(a)
}
Раньше
trait ToBytes{
def bytes(&self): Vec<u8>
}
Rust
10 / 10
Ещё фишки
class Animal(name: String)
val animal = Animal("Cow")
open class Animal(val name: String)
import scala.language.adhocExtensions
@transparentTrait trait SeqOps
4 / 10
Типы
enum
enum Option[+T]:
case Some(x: T)
case None
enum Color:
case Red, Green, Blue
sealed trait Option[+T] extends Product with Serializable
object Option{
final case class Some[+T](a: T) extends Option[T]
case object None extends Option[Nothing]
}
sealed trait Color
object Color{
case object Red extends Color
case object Green extends Color
case object Blue extends Color
}
Scala 2
Scala 3
8 / 10
Intersection and Union
trait Resettable:
def reset(): Unit
trait Growable[T]:
def add(t: T): Unit
def f(x: Resettable & Growable[String]) =
x.reset()
x.add("first")
case class UserName(name: String)
case class Password(hash: Hash)
def help(id: UserName | Password) =
val user = id match
case UserName(name) => lookupName(name)
case Password(hash) => lookupPassword(hash)
8 / 10
& Intersection
| Union
Function types
trait Collection:
type Item
val mapper: (col: Collection) => (col.Item => col.Item) => Collection =
Dependent function
val getter: [A] => Vector[A] => A =
[A] => (v: Vector[A]) => v(1)
val mapper: [A] => (A => A) => List[A] => List[A] =
[A] => (f: (A => A)) => (l: List[A]) => l.map(f)
Polymorphic function
Contextual function
val expr : Consumer[Int] ?=> Unit =
consume(1)
consume(2)
consume(3)
8 / 10
Opaque type alias
object ClientId:
opaque type ClientId <: String = String
def apply(id: String): ClientId = id
given Show[ClientId] = Show[String]
given Eq[ClientId] = Eq[String]
given Encoder[ClientId] = Encoder[String]
given Decoder[ClientId] = Decoder[String]
type ClientId = ClientId.ClientId
val id: ClientId = ClientId("cli:12356")
// ! val id: ClientId = "cli:123456" !
@derive(show, eq, encoder, decoder)
@newsubtype
case class CliendId(id: String)
5 / 10
Scala 3
Scala 2
Контексты
Тайпклассы
Моноид
trait Monoid[A]:
def combine(x: A, y: A): A
def empty: A
extension [A](xs: List[A])
def collectWith[B](f: A => B)(using B: Monoid[B]): B =
xs.view.map(f).foldLeft(B.empty)(B.combine)
def collect(using Monoid[A]): A =
collectWith(x => x)
Моноид
trait Monoid[A]:
def combine(x: A, y: A): A
def empty: A
object Monoid:
given Monoid[String] with
def combine(x: String, y: String) = x + y
def empty = ""
given Monoid[Int] with
def combine(x: Int, y: Int) = x + y
def empty = 0
given [A]: Monoid[List[A]] with
def combine(x: List[A], y: List[A]) = x ::: y
def empty = Nil
given [A, B](using A: Monoid[A], B: Monoid[B]): Monoid[(A, B)] with
def combine(x: (A, B), y: (A, B)) =
(A.combine(x._1, y._1), B.combine(x._2, y._2))
def empty = (A.empty, B.empty)
Моноид
trait Monoid[A]:
def combine(x: A, y: A): A
def empty: A
extension (x: A)
infix def |+|(y: A): A = combine(x, y)
def empty[A](using A: Monoid[A]): A = A.empty
extension [A](xs: Vector[A])
def collectWith[B: Monoid](f: A => B): B =
xs.view.map(f).foldLeft(empty)(_ |+| _)
Scala 2
trait Monoid[A]{
def combine(x: A, y: A): A
def empty: A
}
object Monoid{
implicit val stringMonoid: Monoid[String] = new Monoid[String] {
def combine(x: String, y: String) = x + y
def empty = ""
}
implicit val intMonoid: Monoid[Int] = new Monoid[Int]{
def combine(x: Int, y: Int) = x + y
def empty = 0
}
implicit def listMonoid[A]: Monoid[List[A]] = new Monoid[List[A]]{
def combine(x: List[A], y: List[A]) = x ::: y
def empty = Nil
}
implicit def tupleMonoid[A, B](implicit A: Monoid[A], B: Monoid[B]): Monoid[(A, B)] = new Monoid[(A, B)] {
def combine(x: (A, B), y: (A, B)) =
(A.combine(x._1, y._1), B.combine(x._2, y._2))
def empty = (A.empty, B.empty)
}
}
Scala 2
object monoidSyntax{
implicit class monoidSyntax[A](private val x: A) extends AnyVal{
def |+|(y: A)(implicit A: Monoid[A]): A = x |+| y
}
}
import monoidSyntax._
extension (x: A)
infix def |+|(y: A): A = combine(x, y)
Context Functions
typealias ClickHandler = context(Button) (ClickEvent) -> Unit
Kotlin (KEEP-259)
Scala
type ClickHandler = Button ?=> ClickEvent => Unit
Context Functions
import scala.collection.mutable.ArrayBuffer
class Table:
val rows = new ArrayBuffer[Row]
def add(r: Row): Unit = rows += r
override def toString = rows.mkString("Table(", ", ", ")")
class Row:
val cells = new ArrayBuffer[Cell]
def add(c: Cell): Unit = cells += c
override def toString = cells.mkString("Row(", ", ", ")")
case class Cell(elem: String)
def table(init: Table ?=> Unit) =
given t: Table = Table()
init
t
def row(init: Row ?=> Unit)(using t: Table) =
given r: Row = Row()
init
t.add(r)
def cell(str: String)(using r: Row) =
r.add(new Cell(str))
table {
row {
cell("top left")
cell("top right")
}
row {
cell("bottom left")
cell("bottom right")
}
}
10 / 10
Derivation
trait JsonDecoder[A]:
def decode(json: Json): Either[String, A]
object JsonDecoder:
inline def derived[A](using Mirror[A]): Decoder[A] = ...
case class BusinessEntity(...) derives JsonDecoder, JsonEncoder, Swagger
enum Status derives Show, Eq, JsonDecoder, JsonEncoder:
case Open, InProcess, Closed
7 / 10
Метапрограммирование
Матч-типы
type Elem[X] = X match
case String => Char
case Array[t] => t
case Iterable[t] => t
type Concat[Xs <: Tuple, +Ys <: Tuple] <: Tuple = Xs match
case EmptyTuple => Ys
case x *: xs => x *: Concat[xs, Ys]
def leafElem[X](x: X): LeafElem[X] = x match
case x: String => x.charAt(0)
case x: Array[t] => leafElem(x(9))
case x: Iterable[t] => leafElem(x.head)
case x: AnyVal => x
Inlines
object Config:
inline val logging = false
object Logger:
inline def log[T](msg: String)(op: => T): T =
if Config.logging then
println(s"start $msg")
val result = op
println(s"$msg = $result")
result
else op
def factorial(n: BigInt): BigInt =
log(s"factorial($n)") {
if n == 0 then 1
else n * factorial(n - 1)
}
Inlines
object Config:
inline val logging = false
object Logger:
inline def log[T](msg: String)(op: => T): T =
if Config.logging then
println(s"start $msg")
val result = op
println(s"$msg = $result")
result
else op
def factorial(n: BigInt): BigInt =
log(s"factorial($n)") {
if n == 0 then 1
else n * factorial(n - 1)
}
def factorial(n: BigInt): BigInt =
if n == 0 then 1
else n * factorial(n - 1)
logging = false
def factorial(n: BigInt): BigInt =
println(s"start factorial($n)")
val result =
if n == 0 then 1
else n * factorial(n - 1)
println(s"factorial($n) = result")
result
logging = true
Compile-time ops
transparent inline def toIntC[N]: Int =
inline constValue[N] match
case 0 => 0
case _: S[n1] => 1 + toIntC[n1]
inline def defaultValue[T] =
inline erasedValue[T] match
case _: Byte => Some(0: Byte)
case _: Char => Some(0: Char)
case _: Short => Some(0: Short)
case _: Int => Some(0)
inline def setFor[T]: Set[T] = summonFrom {
case ord: Ordering[T] => new TreeSet[T](using ord)
case _ => new HashSet[T]
}
Macros
inline def assert(inline expr: Boolean): Unit =
${ assertImpl('expr) }
def assertImpl(expr: Expr[Boolean])(using Quotes) = '{
if !$expr then
throw AssertionError(s"failed assertion: ${${ showExpr(expr) }}")
}
def showExpr(expr: Expr[Boolean])(using Quotes): Expr[String] = Expr(expr.show)
assert(x * y == 2)
9 / 10
Фичи
Авто - Специализация
class RbTree[
@specialized A,
@specialized B]
class TreeMap[
@specialized A,
@specialized B](tree: RbTree[A, B]){
def get(k: A): Option[B]
}
Scala 2
class RbTree[A, B]
class TreeMap[A, B](tree: RbTree[A, B]){
def get(a: A): Option[B]
}
Scala 3
class TreeMap$sp$i$sp$d extends TreeMap<Integer, Double>{
private RbTree$sp$i$sp$d tree
Option<Double> get(Integer a){
return get$sp$i(a)
}
Option<Double> get$sp$i(int a)
}
class TreeMap$sp$i$sp$d extends TreeMap<Integer, Double>{
private RbTree$sp$i$sp$d tree
Option<Double> get(Integer a){
return get$sp$i$d(a)
}
Option$sp$d get$sp$i(int a)
}
Авто - Специализация
ЗАБРОШЕНО
0 / 10
Macro annotations
@Optics
@JsonCodec
@Config
case class AppConfig(
db: DatabaseConfig,
http: HttpConfig,
...
)
0 / 10
Rating Overall
8 / 10
Обзор Scala 3
By Oleg Nizhnik
Обзор Scala 3
- 1,985