Skip to content

Instantly share code, notes, and snippets.

@pofl
Last active March 15, 2019 21:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pofl/b95dfe4d487ee04df04f7ee1635f3777 to your computer and use it in GitHub Desktop.
Save pofl/b95dfe4d487ee04df04f7ee1635f3777 to your computer and use it in GitHub Desktop.
[German] CLT-Vortrag: Scala - Das bessere Java?
.metals/metals.log
.metals/metals.trace.db
public static class A {
public static void main(String[] args) {
int N = 9;
int[] elems = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
int sum = 0;
for (int i = 0; i < N; i++) {
if (i % 3 == 0) sum += elems[i];
}
}
}
public static class Friend {
public String nickname;
public Bool nicknameIsPopulated;
}
public enum AggroState {
IDLE,
CHASING,
FIGHTING;
}
public class MonsterAI {
public AggroState aggroState;
public float aggroRadius;
public PlayerId target;
public Timer chaseTimer;
}
object IntroSyntax {
object HelloWorld extends App {
println("Hello, world!")
}
def sendLetter(letter: String): Unit = ???
def greet(name: String = "World"): String = {
s"Hello ${name}"
}
def writeLetter(): String = {
val greeting = greet("Tux")
s"""${greeting},
|Lorem ipsum. Et eius facilis praesentium
|consectetur. Vitae aut nam maiores harum
|omnis amet. Sint exercitationem vero
|aliquam ipsam et deserunt.""".stripMargin
}
}
object ExpressionsControlStructures {
val PENGUIN_MAX_AGE: Int = 30
case class Penguin(age: Int)
val tux = Penguin(21)
val isTuxStillAlive: Bool = {
val timeToLive = PENGUIN_MAX_AGE - tux.age
val isAlive = if (timeToLive > 0) true else false
isAlive
}
}
object ExpressionsLoops {
@tailrec
def sumMultiplesOfThree(elems: Seq[Int],
sum: Int = 0): Int =
if (elems.isEmpty) sum else {
val nextSum = sum + elem.head
sumMultiplesOfThree(elems.tail, nextSum)
}
val sumOfThrees = sumMultiplesOfThree(elementList)
object ListCombinators {
class List[A] {
def filter(func: (A) => Boolean): List[A]
def foreach(func: (A) => Unit): Unit
def map[B](func: (A) => B): List[B]
def foldLeft[B](init: B)(func: (B, A) => B): B
}
}
val elementList = 1 to 9
val sum = {
var sum = 0
elementList.filter(i => i % 3 == 0).foreach(i => sum += i)
sum
}
val sum = elementList.filter(i => i % 3 == 0).sum
val sum = elementList.filter(_ % 3 == 0).sum
val sum =
elementList
.filter(_ % 3 == 0)
.foldLeft(0)((sum, nextElem) => sum + nextElem)
}
object ValVar {
var a = 5
a = 3
a += 5
val b = 5
b = 3 // Fehler
b += 5 // Fehler
}
object FirstClassFunctions {
def sendRequestToIp(
ip: String,
req: Request): Response = ???
// in production
val sendRequest = sendRequestToIp(config.serverIp, _)
val res = doActualWork(sendRequest)
def sendRequestToIp(ip: String,
req: Request): Response = ???
def doActualWork(send: Request => Response): AwesomeResult = ???
// for tests
val sendRequestForTesting = sendRequestToIp("127.0.0.1", _)
val res = doActualWork(sendRequestForTesting)
def sendRequestToIp(ip: String,
req: Request): Response = ???
def doActualWork(send: Request => Response): AwesomeResult = ???
// for unittests
def mockSendRequest(req: Request): Response =
if (req == testCaseReq) testCaseResp
else throw Exception()
val res = doActualWork(mockSendRequest)
}
object Purity {
def greet(name: String): String = s"Hello, ${name}"
def greet(name: String): Unit = println(s"Hello, ${name}")
def greet: String = s"Hello, ${getUserName()}"
}
object ADTAnatomie {
case class ProductType(num: Int, ask: Bool)
case class HttpResponse(status: Int,
headers: Map[String, String],
body: String)
// Error is a sum type
sealed trait Error
case object Timeout extends Error
case class UnknownStatusCode(statusCode: Int) extends Error
case object BodyIsInvalidJSON extends Error
def errorToMsg(err: Error): String =
err match {
case Timeout => "The server took too long to respond"
case UnknownStatusCode(status) =>
s"The server returned an unknown status code of ${status}"
case BodyIsInvalidJSON =>
"The response body was ill-formed JSON"
}
}
object ADTOption {
val PENGUIN_MAX_AGE: Int = 30
case class Penguin(age: Int)
val penguinMap: Map[String, Penguin] = Map.empty
object OptionDef {
sealed trait Option[A]
case class Some(value: A) extends Option[A]
case object None extends Option
}
val penguinMap: Map[String, Penguin]
val penguinOpt: Option[Penguin] = penguinMap.get(name)
penguinOpt match {
case Some(value) => println(s"The Option contains ${value}")
case None => println(s"The Option is empty")
}
val timeToLive: Option[Int] = penguinOpt match {
case Some(user) =>
val ttl: Int = PENGUIN_MAX_AGE - user.age
Some(ttl)
case None => None
}
val timeToLive =
penguinMap.get(id).map(PENGUIN_MAX_AGE - _.age)
}
object ADTEither {
object EitherDef {
sealed trait Either[A, B]
case class Left(value: A) extends Either
case class Right(value: B) extends Either
}
def sendHttpRequest(req: HttpRequest): Either[Error, HttpResponse]
val res = sendHttpRequest(HttpRequest(ip, endpoint, "GET"))
val printMsg: String = res match {
case Right(HttpResponse(respStatus, respBody)) =>
s"Status was ${respStatus} and the body is: ${respBody}"
case Left(err) => errorToMsg(err)
}
}
object ADTValidStates {
trait AggroState
case class Idle(aggroRadius: Float) extends AggroState
case class Chasing(target: PlayerId,
chaseTimer: Timer) extends AggroState
case class Fighting(target: PlayerId) extends AggroState
class MonsterAI (val aggroState: AggroState)
}
object ClassSyntaxCompanionObject {
val url = ""
case class HttpRequest(url: String, method: String, body: String)
class Penguin(val name: String, given_age: Int) {
var age: Int = given_age
private var spouse: Option[Penguin] = None
def marryTo(newSpouse: Penguin): Unit =
spouse = Some(newSpouse)
def timeToLive: Int = Penguin.MAX_AGE - age
}
object Penguin {
val MAX_AGE = 30
def newBorn(name: String): Penguin = new Penguin(name, 0)
def apply(name: String, given_age: Int): Penguin =
new Penguin(name, given_age)
}
}
object Main extends App {
/*
- OOP
- [x] Syntax
- [x] class
- [x] object
- [-] trait
- [x] case class
- [x] ADT
- [x] Option
- [x] Either
- FP
- [ ] first-class functions
- [ ] immutability
- `val`s
- immutable collections
- [ ] pure functions
- no side-effects, referential transparency, equational reasoning
- [ ] laziness
- allows declarative instead of imperative programming
- [ ] everything is an expression
- [ ] no loops, (tail) recursion instead, or higher-order functions
- Implicits
- [ ] implicit parameters
- [ ] implicit conversions
- [ ] implicit classes
*/
def sendLetter(letter: String): Unit = ???
def helloFactory(name: String = "World"): String = {
s"Hello ${name}"
}
def writeLetter(): String = {
val greeting = helloFactory("Tux")
s"""${greeting},
|
|Lorem ipsum. Et eius facilis praesentium consectetur. Vitae aut nam
|maiores harum omnis amet. Sint exercitationem vero aliquam ipsam et
|deserunt velit sapiente. Delectus quam illum necessitatibus. Aut
|mollitia quae voluptate excepturi totam consequatur.""".stripMargin
}
///////////////////////////////////////////////////////
3 + 2 == 3.+(2)
class Penguin(private val name: String, given_age: Int) {
var age: Int = given_age
private var spouse: Penguin = null
def marryTo(new_spouse: Penguin): Unit = spouse = new_spouse
}
val tux = new Penguin("tux", 21)
val trillian = new Penguin("Trillian", 23)
tux marryTo trillian // same as tux.marryTo(trillian)
val PENGUIN_MAX_AGE = 30
val isTuxStillAlive = {
val timeToLive = PENGUIN_MAX_AGE - tux.age
val isAlive = if (timeToLive > 0) true else false
isAlive
}
///////////////////////////////////////////////////////
// companion object
object List {
def empty[A](): List[A] = ???
def apply[A](xs: A*): List[A] = ???
}
class List[A] {
def isEmpty: Boolean
def head: A
def map[B](f: A => B): List[B]
def foreach(f: A => Unit): Unit
}
object Penguin {
val MAX_AGE = 30
def newBorn(name: String): Penguin = new Penguin(name, 0)
def apply(name: String, given_age: Int): Penguin = new Penguin(name, given_age)
}
class Penguin(private val name: String, given_age: Int) {
var age: Int = given_age
private var spouse: Penguin = null
def marryTo(new_spouse: Penguin): Unit = spouse = new_spouse
def timeToLive: Int = Penguin.MAX_AGE - age
}
///////////////////////////////////////////////////////
// trait
trait Traversable {
def foreach[U](f: Elem => U)
}
trait
///////////////////////////////////////////////////////
// Option, ADT, pattern matching, case class
def getUserTimeLeftOnEarth(penguinList: Set[Penguin], name: String): Option[Int] = {
val user: Option[Penguin] = penguinList.find(penguin => penguin.name == name)
val timeToLive: Option[Int] = user match {
case Some(user) =>
val ttl = Penguin.MAX_AGE - user.age
Some(ttl)
case None => None
}
}
def getUserTimeLeftOnEarth(penguinList: Set[Penguin], name: String): Option[Int] =
penguinList.find(_.name == name).map(Penguin.MAX_AGE - _.age)
///////////////////////////////////////////////////////
// Either, ADT, pattern matching, case class
case class HttpResponse(status: Int, body: String)
trait Error
case object Timeout extends Error
case class UnknownStatusCodeunknown(status: Int) extends Error
case object MalformedResponse extends Error
val req = HttpRequest("GET", "", "")
val res = sendHttpRequest(req)
val printMsg = res match {
case Right(HttpResponse(respStatus, respBody)) =>
s"Status was ${respStatus} and the body is: ${respBody}"
case Left(err) => err match {
case Timeout => "The server took too long to respond"
case UnknownStatusCode(status) =>
s"The server server returned an unknown status code of ${status}"
case MalformedResponse => "The resposnse was ill-formed."
}
}
println(printMsg)
///////////////////////////////////////////////////////
// case class
case class Message(sender: String, recipient: String, body: String)
val message2 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?")
val message3 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?")
val messagesAreTheSame = message2 == message3 // true
///////////////////////////////////////////////////////
// first-class functions
def realHttpRequestSender(url: String): HttpResponse = ???
def userServiceProxyFactory(
ipAddress: String,
sendRequest: String => HttpResponse
): (String, String) => HttpResponse = {
(user: String, query: String) => sendRequest(s"https://${ipAddress}/users/${user}?${query}")
}
val userServiceProxy: (String, String) => HttpResponse =
userServiceProxyFactory(config.userServiceIp, realHttpRequestSender)
val gentleTorvalds = userServiceProxy("linus", "mood=kind") // https://127.0.0.1/users/linus?mood=kind
val tux = (query: String) => userServiceProxy("tux", query)
val newbornTux = tux("age=0") // https://127.0.0.1/users/tux?age=0
val chemnitzTux = tux("location=Chemnitz") // https://127.0.0.1/users/tux?location=Chemnitz
///////////////////////////////////////////////////////
}
  • [-] beispiele für die diskussion der studienergebnisse
  • monotone sprache intonierung
  • Keine sätze in stichpunkten
  • schriftkontrast
  • immmutability folie umordnen
  • farben und gróße - high contrast theme
  • higher-order functions anders visualisieren
  • haskell rauslassen oder eher einführen
  • mehr folien weniger content pro folie
  • wenn code disktutiert wird, code zeigen
  • java code als java code identifizieren
  • "sum" im java code kein guter variablenname
  • fehler foreach
  • a -> elems
  • sumA -> sum
  • jede variante einzeln
  • foldLeft
  • regular expression an der stelle != regex etc
  • FP eigenschaften kürzer. ein stichounkt -> quintessenz, ein beispiel
  • option mit beispiel anfangen nud dann erklärung
  • sagen dass ich in dem beispiel jetzt typen exploizit ausweise - vielleicht weglassen
  • zwei beispiele draus machen
  • option definition weglassen
  • Friend ... einfügen
  • code mit bäumen visualisieren
  • bei either beispiel die implementation von sendHttpRequest oder einfacheres beispiel
  • right und left sind zusammen mit either in der standard lib implementiert
  • beispiele vereinfachen
  • genauer überlegen, was du sagen willst

Scala - Das bessere Java?

  • Disclaimer:
    • In diesem Vortrag werden Meinungen geäußert
    • Ich bin kein Fan von Germanismen
      • bitte zuerst den eigenen Nachbarn fragen

0 Studie: Managed, Statically-typed, strongly-typed, functional languages besser

Slide beinhaltet eine Illustration der Hauptergebnisse der Studie:

  • strong > weak typing
  • static > dynamic typing
  • managed mem > malloc+free
  • functional > procedural
  • Strongly typed
    • "5" + 3 ... was passiert? "53" oder 8 oder Exception?
  • Statically typed
    • dynamische Sprachen:
      • kompilieren schneller
      • aber! falls strongly typed: möglicherweise exceptions zur Laufzeit
      • oder falls weakly typed: Programm läuft mit unbemerkt falschem Wert weiter
      • Typen müssen teilweise in Unittests oder gar zur Laufzeit manuell überprüft werden
    • statically typed:
      • kompilieren langsamer
      • dafür wird eine ganze Kategorie von Laufzeitfehlern ausgeschlossen -> Korrektere Programme, weniger Tests
      • viele dynamische Sprachen liefern statische Typprüfung nach: TypeScript, Python, PHP (in Diskussion)
      • Ein Typsystem ermöglicht dein teilweisen Beweis der Korrektheit eines Programmes zur Laufzeit. Scala hat sehr mächtiges Typsystem -> mehr compile-time Korrektheit
  • Language-Matrix
    • weak + dynamic : JavaScript
    • weak + static : C
    • strong + dynamic : Python
    • strong + static : Scala
  • Managed
    • Manuelle Speicherverwaltung (C/C++) fehleranfällig (double free, use after free, false free)
    • Garbage-collection birgt zwar (nicht mehr so große) Performance-Einbußen, aber dafür wird eine ganze Kategorie von Laufzeitfehlern ausgeschlossen und die Programmierung vereinfacht
    • Spezialfall Rust
  • Funktional

1 Intro, Einordnung

Stichpunkte:

  • Kompiliert zu Java-Bytecode​ oder JavaScript
  • Interop mit Java-Ökosystem​
  • Vereinigung von Objekt-Orientierung und Funktionaler Programmierung in einer Sprache​
  • Assistiert beim Schreiben korrekter Programme​
  • Einfluss auf viele andere Sprachen
object HelloWorld extends App {
  println("Hello, world!")
}
  • JVM​
  • 2003 von Martin Odersky ins Leben gerufen​. Martin Odersky:​
    • Professor für Programmiersprachen in der Schweiz​
    • Hat vorher um die Jahrtausendwende die theoretische ​Vorarbeit geleistet, um Generics in Java einzuführen​
  • Aktuell Version 2.12.8​, ca 2020 Version 3​
  • Sprache der Wahl bei Hylastix​
  • Weitere Nutzer sind Twitter, Zalando, Guardian​
  • Headline-Projekte sind Spark, Kafka, Akka, Play Framework​
  • OOFP einzigartig
  • Innovativer, sehr kompakte Syntax mit minimalem Boilerplate​
  • Einfluss auf andere Sprachen
  • "the new java"

2 Syntax

def sendLetter(letter: String): Unit = ???

def helloFactory(name: String = "World"): String = {
  s"Hello ${name}"
}

def writeLetter(): String = {
  val greeting = helloFactory("Tux")

  s"""${greeting},
     |
     |Lorem ipsum. Et eius facilis praesentium consectetur. Vitae aut nam
     |maiores harum omnis amet. Sint exercitationem vero aliquam ipsam et
     |deserunt velit sapiente. Delectus quam illum necessitatibus. Aut
     |mollitia quae voluptate excepturi totam consequatur.""".stripMargin
}
  • Typ-Inferenz​
  • Literale sind Objekte​
  • Leere Klammern können weggelassen werden​
  • String-Literale: s"" und """​
  • Argument-Defaults​
  • Kein return
  • Sie sehen:
    • Zwei Funktionen, die untere ruft die obere auf
  • Type inference
    1. Variablen können immer ohne Typ deklariert werden
    2. Nur Konstruktoren, Funktionen werden annotiert
  • Literals sind Objekte 2.toString()
  • Leere Klammern können weggelassen werden
  • Keine Semikolons, kein return
  • s-String literals, “””

3 Funktionale Programmierung

3.1 Intro

  • Erleichtert Entwicklung von korrekten Programmen​
  • "If it compiles, it does what you want"​
  • Bringt Programmierung näher an Mathematik heran​
    • x = x + 1 !?
  • Ist Programmierparadigma und -stil
  • Erleichtert Schreiben von korrekten Programmen, weniger Tests, vor allem bei concurrent programming
  • Spruch aus der Haskell-Community: If it compiles it does what you want
  • FP bringt Programmieren näher an Mathematik heran
    • x = x + 1 !?
      • Mathematiker würden imperativen Code nicht kompilieren
  • Besteht aus ein paar Features
    • einige müssen von der Sprache unterstützt werden
    • andere sind eher Prinzipien, die vom Kompiler erzwungen werden können aber auch händisch befolgt werden können
    • die stelle ich jetzt vor

3.5 Everything is an expression

  • Kontrollstrukturen (if-else, Schleifen) passen nicht in die funktionale Welt
    • Scala macht aus Kontrollstrukturen einfach Ausdrücke
    • Für Schleifen gibt es bessere Alternativen
  • Die eben gezeichnete Vision "Mein Programm ist eine Gleichung" ist in herkömmlichen Sprachen sicherlich nicht unmöglich, aber um einiges schwieriger
  • Imperative Sprachen basieren auf der Beschreibung von Anweisungen an den Computer
    • Tu dies, weise dieser Variable diesen neuen Wert zu, dann mach das mit der Variable und einem anderen Wert
  • Das allein reicht aber nicht sondern man braucht auch noch Kontrollstrukturen
    • Kurz über die miserable Übersetzung von Kontrollstrukturen aufregen
    • Verzweigung und Schleifen
  • Das Problem dieser Kontrollstrukturen ist dass sie keinen Platz in FP haben, weil sie kein Ausdruck sind - In FP, everything is an expression
  • Scala macht aus Kontrollstrukturen einfach Ausdrücke, indem sie einen "Rückgabewert" erhalten
    • Schleifen sind
val isTuxStillAlive = {
  val timeToLive = Penguin.MAX_AGE - tux.age
  val isAlive = if (timeToLive > 0) true else false
  isAlive
}
int N = 9;
int[] elems = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
int sum = 0;
for (int i = 0; i < N; i++) {
  if (i % 3 == 0) sum += elems[i];
}
val elems = 1 to 9
var sumA = 0
a.filter(i => i % 3 == 0).foreach(sumA + _)
val sumB = 
  elems
    .filter(_ % 3 == 0)
    .foldLeft(0)((sum, nextElem) => sum + nextElem)
val sumC = (1 to 9).filter(_ % 3 == 0).sum

@tailrec
def sumMultiplesOfThree(elems: Seq[Int], sum: Int = 0): Int = 
  elems match {
    case head :: tail => sumMultiplesOfThree(tail, sum + head)
    case Nil => sum
  }
val sumD = sumMultiplesOfThree(elems)

3.2 Immutability

  • Der Wert von Variablen kann nicht geändert werden. Wenn mit Werten gerechnet wird, wird das Ergebnis in eine neue Variable geschrieben oder anderweitig verwendet
    • Die meisten nicht-funktionalen Sprachen unterstützen dies schon mit Schlüsselwörtern wie const oder final, Scala hat val und var
  • Wenn sich Werte nicht ändern können, hat das Programm weniger 'bewegliche Teile'
  • -> das Programm wird begreifbarer
  • Das erfordert anfangs eine gehörige Umgewöhnung, man kann sich am Anfang nicht mal vorstellen, wie das gehen soll
  • -> Concurrency wird handhabbarer, kein shared state, data races sind unmöglich, thread-safety by design
    • Das ist sowieso ein Trend im Bereich Concurrency: kein shared-state
      • "Don't communicate by sharing memory, share memory through communication"
  • immutable collections - wie funktionieren sie?

3.3 First-class Functions

  • Sprache erlaubt für Funktionen die gleichen Freiheiten wie für Variablen oder Objekte​
  • "Higher-order functions" = Funktionen können andere Funktionen als Parameter akzeptieren und Funktionen zurück geben​
  • "First-class functions" fordert darüber hinaus, dass Funktionen Variablen zugewiesen werden können und dass Funktionen zur Laufzeit erstellt werden können
  • Nutzerservice, an den man über eine URL Anfragen/Befehle schicken kann​
  • IP-Adresse des Service per Config zum Programmstart​
def userServiceProxyFactory(ipAddress: String,
                            sendRequest: String => HttpResponse
): (String, String) => HttpResponse =
  (user: String, query: String) =>
    sendRequest(s"https://${ipAddress}/users/${user}?${query}")

def realHttpRequestSender(url: String): HttpResponse = ???
val userServiceProxy: (String, String) => HttpResponse =
  userServiceProxyFactory(config.userServiceIp,
                          realHttpRequestSender)
val gentleTorvalds = userServiceProxy("linus", "mood=kind")
// --> https://127.0.0.1/users/linus?mood=kind
val tux = userServiceProxy("tux", _)
val newbornTux = tux("age=0")
// --> https://127.0.0.1/users/tux?age=0
val chemnitzTux = tux("location=Chemnitz")
// --> https://127.0.0.1/users/tux?location=Chemnitz
  • assign functions to variables, return from or pass to other functions, create at runtime
    • immer mehr Sprachen führen lambdas ein
    • Note that in this context you may often face another related term as “Higher-order functions.” To be precise, higher order functions is a limited feature compared to first-class functions, which allows a developer to pass functions as an argument or return as a result of function evaluation, but not necessarily creating them in runtime. In that way, a procedural language like C supports higher-order functions, but not first-class functions, therefore, cannot be called a functional language.
  • higher-order functions
    • map, foreach, filter ersetzen Schleifen
  • Welche Design patterns werden dadurch obsolet?

3.4 pure functions

  • Reine Funktionen​
    • Eine Funktion hat keinerlei Nebenwirkungen​
    • Gleiche Inputs = gleicher Output​
  • Vorteile:​
    • Einfachere Tests​
    • Compiler kann optimieren
    • Referential transparency / equational reasoning​
  • "Eine Funktion hat keinerlei Nebenwirkungen​"

    • Dh. während ihrer Ausführung werden keine funktionsexternen Dinge geändert
      • globale Variablen, Dateien, keine externen Anfragen an irgendwas abgeschickt
      • im Engeren Sinne darf auch nichts in die Konsole geschrieben werden
  • "Gleiche Inputs = gleicher Output"

    • Funktion darf nicht nur nichts Externes verändern oder beeinflussen sondern auch nichts lesen und das Gelesene benutzen. Darf auch nicht von der Konsole lesen.
    • Ergo: Der Output darf nur von den Inputs abhaengen
  • Purity wird vom Scala-Compiler nicht enforcet

    • Aber Haskell tut dies. Falls sich jemand fragt wie man ein Programm schreibt, das keine Funktionen erlaubt, die mit programmexternen Ressourcen interagieren: Es gibt in der funktionalen Programmierung Techniken mit denen man Nebenwirkungen in einen Datentypen packt und dann auch Instanzen davon erzeugen kann
    • In Scala, schreibe alle Funktionen soweit wie möglich pure, und schreibe unreine Funktionen wenn nötig
  • "Einfachere Tests​"

    • klar, einfach Funktion nehmen, Inputs rein und Ergebnis prüfen
    • wenn du ein Modul hast, das eine Anfrage an einen Server oder an eine DB schickt und das Ergebnis nimmt und eine ganze Reihe an Business Logik Operationen darauf ausführt. Und da ging es mir lange so dass ich mich immer gefragt habe wie man sowas testet. Da braucht man doch eine Datenbanken Instanz zum testen und so weiter. Aber ne, braucht man nicht. Also für richtige Systemtests braucht man sowas schon aber man kann auch so vorgehen: Datenbankenabfrage abkapseln. Eine schmale Funktion bauen, die die Abfrage durchführt und das einfachste mögliche Ergebnis zurückgibt. Die Business-Logik in eine eigene Funktion oder mehrere, die als Input diesen rohen DB-Output nimmt und dann die ganze Logik darauf ausführt. So kannst du im Test auf die Datenbankabfrage verzichten und die Logik einfach mit vorgefertigten Antworten speisen. Wenn die eigentliche IO-Funktion wirklich richtig schmal ist, kannst du uU. aufs testen verzichten weil du Korrektheit mit "Draufgucken" sicherstellen kannst.
  • "Referential transparency / equational reasoning​"

    • Eigenschaft, die ein Ausdruck erfüllen kann
      • "Ausdruck" klären
    • Ausdruck ist RT, wenn ich den Ausdruck durch sein Ergebnis ersetzten kann und das Programm dadurch das selbe bleibt.
      • Dh. der Ausdruck darf keine Side-effects haben bzw allgemeiner: der Ausdruck muss rein sein
      • Funktionsaufrufe werden durch ihren Rumpf ersetzt
    • RT ermöglicht Optimierungen durch den Compiler
    • Wenn ich mein ganzes Programm komplett pur schreibe, dann kann ich von der main aus alle Funktionsaufrufe durch deren Rümpfe ersetzen, alle Ausdrücke auflösen etc => Das Programm wird zu einer gigantischen Gleichung. Und die Gleichung kompiliert nur wenn sie eine Lösung hat => "If it compiles it does what you want" -- Die Vision der funktionalen Programmierung
      • Die Realität ist natürlich nicht so rosig wie diese romantische Geschichte, aber man kommt diesem Traum halt in der Tat mit FP näher als mit anderen Sprachen

4 Summentypen

4.1 Option

  • Benutzt man wenn man ein Wert da sein kann oder nicht
  • Hervorragend geeignet als Rückgabewertcontainer
  • Beispiel: Lookup eines Namens in einer Hashtabelle
def getUserTimeLeftOnEarth(penguinList: Map[String, Penguin],
                           name: String): Option[Int] = {
  val user: Option[Penguin] =
    penguinList.find(penguin => penguin.name == name)
  val timeToLive: Option[Int] = user match {
    case Some(user) => 
      val ttl = Penguin.MAX_AGE - user.age
      Some(ttl)
    case None => None
  }
}
penguinList.find(_.name == name)
           .map(Penguin.MAX_AGE - _.age)
  • Das ist besser als die Java Varianten um zu signalisieren dass etwas entweder da ist oder nicht
    • Jeder weiß vom Funktionskopf, dass die Funktion entweder einen Wert gibt oder nicht
    • Java: Entweder Exception wird geworfen, das ist halbwegs kommunizierbar
    • Oder es wird null zurück gegeben. Das ist echt blöd, weil man die Funktionsimplementation angucken muss um zu wissen, dass der Output auch null sein kann. Wenn man das nicht macht, nutzt man uU. die Funktion einfach, nimmt an dass der Wert immer da ist, und kriegt dann zur Laufzeit eine uncaught exception

4.2 case class und trait

case class Message(sender: String, recipient: String, body: String)

val message2 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?")
val message3 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?")
val messagesAreTheSame = message2 == message3  // true

4.3 Anatomie eines Summentyps

  • Inwiefern "Sum" types?
    • Es geht um den Wertebereich von Datentypen
      • Int -> 2^32 mögliche Werte
      • Char -> 2^7 mögliche Werte
      • Bool -> 2 mögliche Werte
    • "Sum type" -> Wertebereiche werden addiert
      • Entweder-oder Verknüpfung von Werten
  • Option ist ein Beispiel von Sum types:
    • Wertebereich von Option[T] hat alle Werte von T + einen Wert None
trait Option[T]
case class Some[T](value: T) extends Option[T]
case object None extends Option[Nothing]
  • Reduzieren die Menge möglicher Werte -> reduzieren die Menge der invaliden Zustände
public static class Friend {
    public String nickname;
    public Bool nicknameIsPopulated;
}
public enum AggroState {
  IDLE,
  CHASING,
  FIGHTING;
}
public class MonsterAI {
  public AggroState aggroState;
  public float aggroRadius;
  public PlayerId target;
  public Timer chaseTimer;
}
trait AggroState
case class Idle(aggroRadius: Float) extends AggroState
case class Chasing(target: PlayerId,
                   chaseTimer: Timer) extends AggroState
case class Fighting(target: PlayerId) extends AggroState

class MonsterAI (val aggroState: AggroState)

4.4 Either

case class HttpResponse(status: Int, body: String)
trait Error
case object Timeout extends Error
case class UnknownStatusCodeunknown(status: Int) extends Error
case object MalformedResponse extends Error

val res = sendHttpRequest(HttpRequest("GET", "", ""))
val printMsg: String = res match {
  case Right(HttpResponse(respStatus, respBody)) => 
    s"Status was ${respStatus} and the body is: ${respBody}"
  case Left(err) => err match {
    case Timeout => "The server took too long to respond"
    case UnknownStatusCode(status) => 
      s"The server server returned an unknown status code of ${status}"
    case MalformedResponse => "The response was ill-formed."
  }
}

5 OOP

5.1 class (constructor)

class Penguin(val name: String, given_age: Int) {
  var age: Int = given_age
  private var spouse: Option[Penguin] = None
  def marryTo(newSpouse: Penguin): Unit = spouse = Some(newSpouse)
  def timeToLive: Int = Penguin.MAX_AGE - age
}
object Penguin {
  val MAX_AGE = 30
  def newBorn(name: String): Penguin = new Penguin(name, 0)
  def apply(name: String, given_age: Int): Penguin = new Penguin(name, given_age)
}
  • Scala hat kein static Schlüsselwort
  • Companion object beherbergt alle statischen Member einer Klasse
  • Allein stehend erzeugt das Schlüsselwort object ein Singleton

5.2 object, companion object

object List {
  def empty[A](): List[A] = ???
  def apply[A](xs: A*): List[A] = ???
}
class List[A] {
  def isEmpty: Boolean
  def head: A
  def map[B](f: A => B): List[B]
  def foreach(f: A => Unit): Unit
}

object Penguin {
  val MAX_AGE = 30
  def newBorn(name: String): Penguin = new Penguin(name, 0)
  def apply(name: String, given_age: Int): Penguin = new Penguin(name, given_age)
}
class Penguin(private val name: String, given_age: Int) {
  var age: Int = given_age
  private var spouse: Penguin = null
  def marryTo(new_spouse: Penguin): Unit = spouse = new_spouse
  def timeToLive: Int = Penguin.MAX_AGE - age
}

6 Implicits

  1. implicit parameters
  2. implicit conversion
  3. Pimp my library with example

7 Fazit

  • Scala ist ein Werkzeug, um korrekte Programme zu schreiben
  • Habe gezeigt inwiefern funktionale Programmierung und Summentypen dabei unterstützen, korrekte Programme zu schreiben
  • Scala ist eine (sehr) funktionale Programmiersprache
  • Viele Sprachen unterstützen die hier genannten funktionalen Features mittlerweile
    • Aber die meisten davon waren nie als FP Sprache gedacht
    • Der Syntax ist doof, die Implementierungen sind work-arounds
  • Scala ist funktionale Sprache von Beginn an
    • Sprache formalisiert weitaus kleiner als andere
    • FP mit Priorität integriert in Sprache
    • Alle Lernmaterialien zielen auf FP
  • Scala ist eine sehr funktionale / mächtige FP Sprache
    • Als FP-Sprache nicht so idiomatisch wie Haskell
      • erlaubt Nebenwirkungen, ist eager
    • Aber Scala hat Sprachfeatures die es möglich gemacht haben, dass es eine Parallelgesellschaft in der Scala-Community gibt, die Bibliotheken gebaut haben, die Kategorientheorie implementieren und somit in Scala FP bis zum Anschlag erlauben
    • Das alles ist aber komplett optional, man kann komplett darauf verzichten (kann man in Haskell nicht)
      • 70% der Haskell features zu 30% des Lernaufwands
  • Scala ist keine schnelle Sprache
  • Einarbeitungszeit von 1-3 Monaten
  • Lange Kompilierzeiten aufgrund von komplexem Typsystem
  • Wer Scala nutzt, für den ist Laufzeit-Performance nicht oberste Priorität
    • Kurzer Exkurs über das Überbewerten von Performanz
    • Schneller als Skriptsprachen
    • Langsamer als kompilierte Sprachen (ofc), wahrscheinlich slightly langsamer als Java, weil einige Konstrukte unter Performanzverlust auf JVM-Features abgebildet werden müssen
    • Funktionale Programmierung insgesamt langsamer als imperative Programmiersprachen, weil letztere näher an Assembly dran sind. Außerdem nimmt man in FP keine Arrays, welche im Allgemeinen schneller sind als Listen
  • Scala ist (nicht) das bessere Java
  • Besser als Java? Ja und Nein.
    • Achtung Meinung: Besser als Java, aber nicht das bessere Java.
    • Besser, weil alle Features abgebildet werden mit einem stimmigeren Syntax, und das mächtige Ecosystem genutzt werden kann.
    • Nicht das bessere Java, weil es andere Paradigmen forciert
    • Kotlin wäre ein besseres Java
  • Scala ist für Unternehmen ein zweischneidiges Schwert
  • Kostenersparnis durch weniger Fehler, wartbaren Code
  • Programmierer sind sehr schwer zu finden / teuer
  • Hoher Lernaufwand
  • Scala ist für Programmierer absolut empfehlenswert
  • Scala-Programmierer werden gesucht
  • Scala ist wunderbar geeignet um sich in Richtung FP fortzubilden
  • zB bei Hylastix
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment