Распаковка 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] = nullUnsoundness


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 pdef 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 ifwhile 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 bKotlin 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).ytrait Foo(val x: String)
trait Bar extends Foo:
    val y = x.toUpperCase
(new Bar with Foo("lol"){}).y9 / 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 SeqOps4 / 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) -> UnitKotlin (KEEP-259)
Scala
type ClickHandler = Button ?=> ClickEvent => UnitContext 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, Closed7 / 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      => xInlines
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")
  resultlogging = 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
- 2,418
 
   
  