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

вопросы:

t.me/scala_ru

t.me/odomontois

эти слайды:

slides.com/olegnizhnik/scala-3