Scala

Mai temak

Scala gyorstalpalo

Makrok

Free monadok

Scala

Funkcionalis + OOP

Blokkstrukturalt (teljes)

C-like szintaxis

JVM-en fut

2.13.1

Dotty ~ Scala 3.x

Teljesen open source

Alapelemek

Osztalyok

def

val/var

lazy val

Helyettesithetoseg elve (dev x/*()*/: Int vs val x: Int)

Minden metodus es mezo osztalyon belul

public*, private, package private, private[this]

Nevek (szinte) barmi

++=, ++:

:-ra vegzodo fuggvenyek jobb-asszociativak

Operatorok is fuggvenyek

Kontrol elemek

if/else if/else

for

while

case

match

Majdnem minden expression Scalaban (=> van visszateresi erteke)

Case classok

Value classok

Algerbai adattipus (product es sum)

Konstruktorban szereplo adattagok alapjan

getters, setters, hashCode, equals, copy, toString, apply, unapply

case object

Nincs oroklodes (ekvivalencia)

Generikusok

Parametrikus polimorfizmus

Osztaly tipusparameter

class Generic[A](member: A)

Fuggveny tipusparameter

def method[A, B](arg: A): B = { ... }

Lambda nem lehet generikus

Tipuskorlatok

Also korlat

class Generic[A >: Int](member: A)

Az A-nak leszarmozottja kell hogy legyen az Int

Ko- es kontravariancia helyessegehez szukseges

Felso korlat

class Generic[A <: Int](member: A)

Az A-nak ososztalya kell hogy legyen

Erdekessegek Scalaban

Makrok

A makrok a Scala metaprogramozas legyegyszerubb eszkozei

Scala kod, ami forditasi idoben Scala kodot general

Nem keverendo ossze a C preprocesszor makrokkal (=egyszeru szoveghelyettesites)

A makro AST-bol AST-t keszit

Megkapja a compiler aktualis allapotat

Amikor a typer egy makro hivast talal, lefuttatja a makro kodjat

A makro altal generalt AST-t a hivas pontos helyere illeszti

Majd lefuttatja az uj AST-re is a typert

Megszoritas: tipushelyes AST-t kell generalnunk

A kimeno AST lehet ugyanaz, mint a bemeno: vegezhetunk ellenorzeseket is csupan

A makro definicioja es a felhaszanalasa lehet ugyanabban a forditasi egysegben

Makro pelda

import scala.reflect.macros.blackbox

object Macros {
    // A macro kulcsszo hasznalata jelzi a compilernek: itt trukkozunk 
    def hello: Unit = macro helloImpl

    // A macro implementacioja mindig kulon van definialva
    // A c itt a compiler context
    // A generalt AST most Unit tipusu
    def helloImpl(c: blackbox.Context): c.Expr[Unit] = {
        // A context path-dependent dolgai
        import c.universe._
        // A q""" egy quasiquote - ezekrol kesobb
        c.Expr(q"""println("hello!")""")
    }
}
import Macros.hello
def usingMyMacro(): {
    ...
    hello // Ez a szimbolum lesz attranszformalva
    ...
}

Makrok fajtai

A makrok altal generalt AST korabban megorizte a visszateresi tipust statikusan

Ez igy nem biztos, hogy jo: specifikusabb tipussal terunk vissza, es az "ugy is marad", tehat nem elvart

Blackbox vs whitebox makrok

Blackbox: pont ugy viselkedik, mint egy sima def: a visszateresi tipusu AST-vel ter vissza

A mukodesuk megertesehez nincs szukseg az implementaciora

Whitebox: nincs feltetlenul pontos szignaturajuk Scalaban

Ez hogyan fordulhat elo? Pl. conditional types

Ez a korabbi mukodes: nincs megkotes a visszateresi tipusra

Mindketto fontos lehet, de: blackbox trivialisabb, konnyebben irhato, ertheto, tamogathato

AST generalasa

Kezzel: letrehozzuk a fat a megfelelo tipusok alkalmazasasaval

c.Expr {
  Apply(
    Ident(TermName("println")),
    List(
      Apply(
        Select(
          Apply(
            Select(
              Literal(Constant("hello ")),
              TermName("$plus")
            ),
            List(
              s.tree
            )
          ),
          TermName("$plus")
        ),
        List(
          Literal(Constant("!"))
        )
      )
    )
  )
}

Keves embernek esik ez jol

AST generalasa

Quasiquote-ok

Van valami infrastrukturank, amit tud szovegbol AST-t kesziteni: a parser!

Ha mar van, hasznaljuk

// Ez ugyanaz az AST lesz, mint az elozo dian
q"""println("hello " + ${s.tree} + "!")"""

A tree reprezentacio kiirasa

showRaw(q"""println("hello " + ${s.tree} + "!")""")

AST generalasa

Reify es splice

Ez is egy makro; jon a compilerrel

reify: kodblokk -> AST

splice: AST -> kodblokk (csak reify blokkon belul mukodik)

// Ez ugyanaz az AST lesz, mint az elozo diakon
reify {
  println(s"hello ${s.splice}!")
}

Itt valodi Scala kodot irunk (a makro forditasakor ellenorzi a compiler)

Konnyebb helyes kodot irni, cserebe kenyelmetlenebb, mint a quasiquote

Makro pelda parameterrel

import scala.reflect.macros.blackbox

object Macros {
    def hello(s: String): Unit = macro helloImpl

    // A makronk masodik parameterlistaja az, ami "exposalva" van a hasznalatra
    // A bejovo AST tipusa egy String tipusu kifejezes
    // Ezt persze kiertekelni nem tudjuk itt - ahhoz a runtime minden informaciojara szukseg lenne
    def helloImpl(c: blackbox.Context)(s: c.Expr[String]): c.Expr[Unit] = {
        import c.universe._
        c.Expr(q"""println("hello " + ${s.tree} + "!")""")
    }
}
import Macros.hello
def usingMyMacro(): {
    ...
    hello("Scala") // Ez a szimbolum lesz attranszformalva
    ...
}

Makro bundle

Eddig mindig csak egyszeru def-eket hasznaltunk makrokent

Trivialis esetkben jol mukodik

Bonyolultabb helyzetben jo lenne tobb defet hasznalni

Enkapszulacio, ujrafelhasznalas, stb.

import scala.reflect.macros.blackbox.Context

class Impl(val c: Context) {
    def hello(s: c.Expr[String]): c.Expr[Unit] = {
        import c.universe._
        c.Expr(q"""println("hello " + ${s.tree} + "!")""")
    }
}

object Macros {
    def hello(s: String): Unit = macro Impl.helloImpl
}

Makro bundle: egy osztaly, aminek az egyetlen konstruktora egy contextet fogad el

Ide tudjuk tenni a makro implementaciokat + az osszes utilityt, amire szuksegunk van

Generikus makrok

Egy makro lehet generikus

Ebben az esetben az applikacio helyen kotelezo explicite megadni a tipusokat

A pontos tipusinformacio megorzese erdekeben nem art TypeTag-et kerni a parameterekre (context bounds)

WeakTypeTag: ez mindig a leheto legkonkretabb tipust fogja leirni

class Queryable[T] {
 def map[U](p: T => U): Queryable[U] = macro QImpl.map[T, U]
}

object QImpl {
 def map[T: c.WeakTypeTag, U: c.WeakTypeTag]
        (c: Context)
        (p: c.Expr[T => U]): c.Expr[Queryable[U]] = ...
}

Makro praktikak

-Ymacro-debug-lite: kiirja a macro expansion helyet, eredmenyet nyers Tree-kent es pszeudo-Scala kodkent

Nem kezelt exceptionok: stacktrace kiirasa, de ez nem tul elegans

Compiler kimenete hibak es figyelmeztetesek hasznalatara

c.abort(c.enclosingPosition, "macro error")
c.error(c.enclosingPosition, "macro compiler error")
c.warning(c.enclosingPosition, "macro warning")