Scala
Scala gyorstalpalo
Makrok
Free monadok
Funkcionalis + OOP
Blokkstrukturalt (teljes)
C-like szintaxis
JVM-en fut
2.13.1
Dotty ~ Scala 3.x
Teljesen open source
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
if/else if/else
for
while
case
match
Majdnem minden expression Scalaban (=> van visszateresi erteke)
Value classok
Algerbai adattipus (product es sum)
Konstruktorban szereplo adattagok alapjan
getters, setters, hashCode, equals, copy, toString, apply, unapply
case object
Nincs oroklodes (ekvivalencia)
Parametrikus polimorfizmus
Osztaly tipusparameter
class Generic[A](member: A)
Fuggveny tipusparameter
def method[A, B](arg: A): B = { ... }
Lambda nem lehet generikus
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
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
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
...
}
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
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
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} + "!")""")
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
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
...
}
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
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]] = ...
}
-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")