Scala : Monads for the Pragmatic Programmer , Composition with Stackable Traits

Publish in

Education

4 views

Please download to get full document.

View again

of 61
All materials on our website are shared by users. If you have any questions about copyright issues, please report us to resolve them. We are always happy to assist you.
Share
Description
Der Workshop bietet einen Überblick über diverese Neuerungen in Scala wie Future- und Try- Monaden und zeigt wie man mit Stackable Traits composition realisiern kann. Es ahndelt sich um einen Workshop mit Live-Coding-Teil Im Detal werden folgende Themen behandelt: Was ist eine Monade, was für Vorteile bringt es für Entwickler das Konzept zu kennen map, flatMap, filter und for-Comprehensions Monaden in Scala: Option-Monade Either-Monade Try (Monadenartig) Future Composition mit Stackable Traits Beispiel zur Dispatch Library (kurz) Specs 2 : Acceptance-Test-Notation
Transcript
  • 1. SCALA FOR GINI - TEIL II MIT MEHR MONADEN ANDREAS NEUMANN !
  • 2. MONADE - FÜR FUNKTIONALE PROGRAMMIERER All told, a monad in X is just a monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor. ! Saunders Mac Lane in Categories for the Working Mathematician
  • 3. MOANDEN FÜR DEN PRAGMATISCHEN ENTWICKLER • Abläufe kappseln ( Wie Objekte Methoden und Werte kappseln ) • Abläufe aneinaderreihen • Seiteneffekte sichtbar machen
  • 4. „WIE EINE MONADE“ • Folgende Operation definiert: • map • flatMap aka bind in other languages • filter • Monadenregeln können verletzt werden, der Programmierer ist dafür selbst verantwortlich (Beispiel später: Try) • => for Notation möglich
  • 5. MAP, FLATMAP, FILTER
  • 6. MAP ! scala> List(1, 2, 3, 4) map (_ + 2) res7: List[Int] = List(3, 4, 5, 6) ! ! scala> List(1, 2, 3, 4) map ( x => x * x) res8: List[Int] = List(1, 4, 9, 16) ! ! scala> List(1, 2, 3, 4) map (_.toDouble) res10: List[Double] = List(1.0, 2.0, 3.0, 4.0)
  • 7. FILTER ! import NaivePrime._ ! scala> (1 to 100) filter isPrime (1 to 100).filter( i => isPrime(i) ) res2: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97) ! scala> (1 to 10) filter ( _ % 3 == 0) res3: scala.collection.immutable.IndexedSeq[Int] = Vector(3, 6, 9) ! scala> List("Januar","Februar","März","April","Mai") filter (month => month.reverse.startsWith("r")) res4: List[String] = List(Januar, Februar)
  • 8. FLATMAP scala> val words = "Gallia est omnis divisa in partes tres" split (" ") words: Array[String] = Array(Gallia, est, omnis, divisa, in, partes, tres) ! scala> words.sliding(2,1).toList res2: List[Array[String]] = List(Array(Gallia, est), Array(est, omnis), Array(omnis, divisa), Array(divisa, in), Array(in, partes), Array(partes, tres)) ! scala> words.sliding(2,1).toList map(bigram => bigram map (_.size)) res3: List[Array[Int]] = List(Array(6, 3), Array(3, 5), Array(5, 6), Array(6, 2), Array(2, 6), Array(6, 4)) ! scala> words.sliding(2,1).toList flatMap(bigram => bigram map (_.size)) res4: List[Int] = List(6, 3, 3, 5, 5, 6, 6, 2, 2, 6, 6, 4)
  • 9. FOR • Vereinfachung von geschachtelten map-, flatMapund Filter-Operation • angelehnt an Pseudocode • unterstützt auch Zuweisungen und Pattern matching
  • 10. FOR - MAP, FLATMAP, FILTER scala> words map f • map res5: Array[Int] = Array(6, 3, 5, 6, 2, 6, 4) ! scala> for (word <- words) yield f(word) res6: Array[Int] = Array(6, 3, 5, 6, 2, 6, 4) ! scala> words filter (_.size > 3) map f • filter res8: Array[Int] = Array(6, 5, 6, 6, 4) ! scala> for (word <- words ; if word.size > 3) yield f(word) res7: Array[Int] = Array(6, 5, 6, 6, 4) ! • flatMap scala> val f : String => Int = s => s.size f: String => Int = <function1> scala> words.sliding(2,1).toList flatMap( bigram => bigram map(word => f(word) ) ) res11: List[Int] = List(6, 3, 3, 5, 5, 6, 6, 2, 2, 6, 6, 4) ! scala> for { bigram <- words.sliding(2,1).toList word <- bigram if word.size > 3 } yield f(word) res10: List[Int] = List(6, 5, 5, 6, 6, 6, 6, 4)
  • 11. NÜTZLICHE MONADEN
  • 12. OPTION-MONADE • Seiteneffekt: Operationen kann oder kann keinen Wert liefern • Option[A] • Ausprägungen: • Some[A] • None
  • 13. OPTION - BENUTZUNG • Die Option-Monade verhält sich wie die ListenMonade • Some( x ) kann man sich wie eine Liste mit einem Element vorstellen • None entspricht der leeren Liste
  • 14. OPTION UND LISTE - VERGLEICH // Like a list scala> Some( 1 ) map (i => i + 0.5 ) res12: Option[Double] = Some(1.5) ! scala> List( 1 ) map (i => i + 0.5 ) res13: List[Double] = List(1.5) ! ! // Like an empty List scala> val x : Option[Int] = None x: Option[Int] = None ! scala> x map (i => i+ 0.5) res16: Option[Double] = None ! scala> val y : List[Int] = List() y: List[Int] = List() ! scala> y map (i => i+ 0.5) res17: List[Double] = List()
  • 15. SZENARIO FÜR OPTION • Wir wollen Wikipedia anfragen • Welche Sprachversion von Wikipedia angefragt werden soll ist konfigurierbar, wir unterstützen nicht alle Sprachen • der Suchterm soll auf Deutsch eingegeben werden und bei Bedarf übersetzt werden, wir kennen nicht alle Übersetzungen • unsere Verbindung ist mist, deshalb kommen die Anfragen nur manchmal an
  • 16. ! ! ! ! // Wikipedia URLs val wiki = Map( "de" -> "http://de.wikipedia.org/wiki", "en" -> "http://en.wikipedia.org/wiki" ) // primitive Translator val translation = Map( "Monade" -> Map("de" -> "Monade", "en" -> "monad"), "Erdferkel" -> Map("en" -> "Aardvark") ) // get Wiki Text in chosen language by providing german Search Term def getWikiTexts(lang: String, searchTerm: String) : Option[String] = for { url <- wiki get lang translations <- translation get searchTerm translatedTerm <- translations get lang answer <- flakyConnection(s"$url/$translatedTerm") } yield answer // the connection might work def flakyConnection(url: String) : Option[String] = if( Random.nextBoolean() ) Some( Source.fromURL(url,"UTF-8").getLines().mkString("").replaceAll("<[^>+]>","") ) else None def example = for { search <- List("Erdferkel","Monade","Erdbeeren") language <- List("en","de") answer <- getWikiTexts(language,search) } yield answer.take(50)
  • 17. BEISPIEL OPTION scala> getWikiTexts("en","Erdferkel") res12: Option[String] = None ! scala> getWikiTexts("en","Erdferkel") res13: Option[String] = Some(<!DOCTYPE html><html lang="en" dir="ltr" class="client-nojs"> ... ! scala> getWikiTexts("de","Monade") res15: Option[String] = Some(<!DOCTYPE html><html lang="de" dir="ltr" class=„client-nojs"> <head><meta charset="UTF-8" /><title>Monade – Wikipedia</title><meta name="generator" content="MediaWiki 1.23wmf5" />...
  • 18. EITHER • wenn None nicht genug ist • liefert entweder den einen oder den anderen Wert • Either[A,B] • Right(x : B) : Either[A,B] • Left(x: A) : Either[A,B]
  • 19. SZENARIO • Wir bowlen mit Walter und dem Dude • Wir übertreten, obwohl es ein Ligaspiel ist und wollen volle Punktzahl eintragen • Entweder wir tragen Null ein oder wir betreten eine Welt des Schmerzes
  • 20. case class WorldOfPain(s: String) case class MarkZero(s: String) ! ! ! ! def markItAnEight : Boolean = Random.nextBoolean() def bowlAndTouchLine : Either[WorldOfPain, MarkZero] = if ( markItAnEight ) Left(WorldOfPain("you are entering a world of pain.")) else Right(MarkZero("All right, it's fucking zero. Are you happy, you crazy fuck?")) def example = { println( """The Dude: Walter, ya know, it's Smokey, so his toe slipped over the line a little, big deal. It's just a game, man. | Walter Sobchak: Dude, this is a league game, this determines who enters the next round robin. Am I wrong? Am I wrong? | Smokey: Yeah, but I wasn't over. Gimme the marker Dude, I'm marking it 8. | Walter Sobchak: [pulls out a gun] Smokey, my friend ...""".stripMargin) ! bowlAndTouchLine match { case Left(x) => println(x) case Right(MarkZero(text)) => { println("Smokey: " + text) println( println("Walter Sobchak: ...It's a league game, Smokey.") ) } } }
  • 21. EITHER EXAMPLE scala> example The Dude: Walter, ya know, it's Smokey, so his toe slipped over the line a little, big deal. It's just a game, man. Walter Sobchak: Dude, this is a league game, this determines who enters the next round robin. Am I wrong? Am I wrong? Smokey: Yeah, but I wasn't over. Gimme the marker Dude, I'm marking it 8. Walter Sobchak: [pulls out a gun] Smokey, my friend ... Smokey: All right, it's fucking zero. Are you happy, you crazy fuck? Walter Sobchak: ...It's a league game, Smokey. () ! scala> ! scala> example The Dude: Walter, ya know, it's Smokey, so his toe slipped over the line a little, big deal. It's just a game, man. Walter Sobchak: Dude, this is a league game, this determines who enters the next round robin. Am I wrong? Am I wrong? Smokey: Yeah, but I wasn't over. Gimme the marker Dude, I'm marking it 8. Walter Sobchak: [pulls out a gun] Smokey, my friend ... WorldOfPain(you are entering a world of pain.)
  • 22. TRY-MONAD-LIKE • Funktionales Error-Handling • Ersetzt Either in vielen Implementierungen • kein echte Monade, da Monadenregeln verletzt werden • Try[A] • Success(x : A) • Failure(e: Exception)
  • 23. TRY EXAMPLE SZENARIO • Wir haben immer noch „crappy Hardware“ und unsere Verbindung ist echt mies • Diesmal schmeißen wir aber mit Exceptions um uns wenn was nicht funktioniert • Es kann passieren, dass keine Verbindung zustande kommt • Es kann passieren, dass die Verbindung beim übertragen der Payload abbricht
  • 24. case class Connection(toWhat: String) { ! def sendData(data: String): Try[String] = Try( send(data) ) ! ! ! ! private def send(paylod: String) = if(Random.nextBoolean) s"Sent payload with ${paylod.size}" else throw new Exception("Couldn't send palyoad over Connection") } object Connection { def getConnection : Try[Connection] = Try ( connect ) ! ! ! def connect = if(Random.nextBoolean) Connection("to the Internetz") else throw new Exception("Couldn't establish connection") } def sendHelloWorld = for { connection <- Connection.getConnection r <- connection.sendData("Hallo Welt") } yield r
  • 25. scala> Connection.getConnection map (_.sendData("Hallo Welt")) res17: scala.util.Try[scala.util.Try[String]] = Success(Failure(java.lang.Exception: Couldn't send palyoad over Connection)) ! scala> Connection.getConnection map (_.sendData("Hallo Welt")) res18: scala.util.Try[scala.util.Try[String]] = Success(Success(Sent payload with 10)) ! scala> Connection.getConnection map (_.sendData("Hallo Welt")) res19: scala.util.Try[scala.util.Try[String]] = Failure(java.lang.Exception: Couldn't establish connection) ! scala> Connection.getConnection map (_.sendData("Hallo Welt")) res20: scala.util.Try[scala.util.Try[String]] = Success(Failure(java.lang.Exception: Couldn't send palyoad over Connection))
  • 26. FUTURE-MONADE • Asynchrone Prozesse • Future[A] • nach on Complete • Success( a: A ) • Failure( e: Exception )
  • 27. FUTURE PLUMBING • ExecutionContext : • Aus der Doku: An ExecutionContext is an abstraction over an entity that can execute ! • import scala.concurrent._ import ExecutionContext.Implicits.global Duration: Implicit Conversions für Int import scala.concurrent.duration._ ! // now valid code 5 seconds 1 hour // langform Duration(100, "millis")
  • 28. EIN FUTURE ERSTELLEN • mit future scala> val h = future { "Hello World!" } h: scala.concurrent.Future[String] = scala.concurrent.impl.Promise$DefaultPromise@2caa89b0 ! scala> h.isCompleted res8: Boolean = true ! scala> h.value res9: Option[scala.util.Try[String]] = Some(Success(Hello World!))
  • 29. FUTURES UND PROMISES • Promise ist ein EINMAL beschreibbarer Container für zukünftige Ergebnisse • Da die Berechnungen scheiten können wird das Ergebnis in ein Try gepackt • Vollendete Promises werden zu Futures • Promise[A] • p.succes.future • p.failure.future
  • 30. PROMISE ERFÜLLEN scala> val p = Promise[String] p: scala.concurrent.Promise[String] = scala.concurrent.impl.Promise$DefaultPromise@37cf00f1 ! scala> p.success("Welcome to the World of Tomorrow!") res4: p.type = scala.concurrent.impl.Promise $DefaultPromise@37cf00f1 ! scala> p.success("!!!") java.lang.IllegalStateException: Promise already completed. at scala.concurrent.Promise$class.complete(Promise.scala:55)
  • 31. PROMISE SCHEITERN LASSEN scala> val p2 = Promise[String] p2: scala.concurrent.Promise[String] = scala.concurrent.impl.Promise $DefaultPromise@612c4f6d ! ! scala> p2.failure( new Exception("Cyrogenic Error") ) res7: p2.type = scala.concurrent.impl.Promise$DefaultPromise@612c4f6d
  • 32. PROMISE ERFÜLLEN UM FUTURE ZU VOLLENDEN scala> p.future res11: scala.concurrent.Future[String] = scala.concurrent.impl.Promise $DefaultPromise@37cf00f1 ! scala> p2.future res12: scala.concurrent.Future[String] = scala.concurrent.impl.Promise $DefaultPromise@612c4f6d ! scala> p.future.value res17: Option[scala.util.Try[String]] = Some(Success(Welcome to the World of Tomorrow!)) ! scala> p2.future.value res15: Option[scala.util.Try[String]] = Some(Failure(java.lang.Exception: Cyrogenic Error))
  • 33. WERT AUS EINEM FUTURE HOLEN scala> val f = futureThatTakes(10)("Nach zehn Sekunden") f: scala.concurrent.Future[String] = scala.concurrent.impl.Promise $DefaultPromise@4a64534b ! // 5 Sekunden ! scala> f.isCompleted res0: Boolean = false scala> f.value res1: Option[scala.util.Try[String]] = None ! // 11 Sekunden ! scala> f.isCompleted res3: Boolean = true scala> f.value res4: Option[scala.util.Try[String]] = Some(Success(Nach zehn Sekunden))
  • 34. FUTURE MIT CALLBACK • • f : Future scala> example res0: List[Unit] = List((), (), ()) f onComplete : Unit scala> Ich brauche fünf Sekunden. Ich brauche zehn Sekunden. Ich brauche elf Sekunden. ! def printFuture[T](f: Future[T]) = f onComplete { case Success(t) => println(t) case Failure(f) => throw f } def example = { val a1 = futureThatTakes(10)("Ich brauche zehn Sekunden.") val a2 = futureThatTakes(5)("Ich brauche fünf Sekunden.") val a3 = futureThatTakes(11)("Ich brauche elf Sekunden.") ! val fs : List[Future[String]] = List(a1,a2,a3) ! ! fs map printFuture }
  • 35. FUTURE ZURÜCK IN DEN SYNCHRONEN PROGRAMMABLAUF ZWEINGEN • Wann fertig ist nicht vorhersehbar • Man kann bestenfalls einen definierten Zeitraum auf das vollenden warten • Await.result( <Future>,< Wartezeit> )
  • 36. // Enough time ! scala> Await.result( futureThatTakes(10)("Ich brauche zehn Sekunden."), 10 seconds ) res6: String = Ich brauche zehn Sekunden. ! ! // Not enough Time ! scala> Await.result( futureThatTakes(10)("Ich brauche zehn Sekunden."), 5 seconds) java.util.concurrent.TimeoutException: Futures timed out after [5 seconds] at scala.concurrent.impl.Promise$DefaultPromise.ready(Promise.scala:219)
  • 37. SEQ[FUTURE[A]] => FUTURE[SEQ[A]] • Wenn man auf die Ergebnisse mehrere Futures zugreifen muss die voneinander abhängig sind scala> | | | | | | lazy val futureSeq = Future.sequence( List( futureThatTakes(10)("Ich brauche zehn Sekunden."), futureThatTakes(5)("Ich brauche fünf Sekunden."), futureThatTakes(12)("Ich brauche zwölf Sekunden.") ) ) futureSeq: scala.concurrent.Future[List[String]] = <lazy> ! ! scala> Await.result(futureSeq, 12 seconds) res8: List[String] = List(Ich brauche zehn Sekunden., Ich brauche fünf Sekunden., Ich brauche zwölf Sekunden.)
  • 38. PITFALLS COMBINING FUTURES • Problem beim kombinieren der Futures def combineDoneWrong = { val combined = for { f1 <- futureThatTakes(10)("Ich brauche zehn Sekunden.") f2 <- futureThatTakes(5)("Ich brauche fünf Sekunden.") } yield f1 + f2 ! Await.result(combined, 11 seconds) }
  • 39. PITFALLS COMBINING FUTURES • Richtig kombiniert def combineDoneRight = { val fF1 = futureThatTakes(10)("Ich brauche zehn Sekunden.") val fF2 = futureThatTakes(5)("Ich brauche fünf Sekunden.") ! val combined = for { f1 <- fF1 f2 <- fF2 } yield f1 + f2 ! Await.result(combined, 11 seconds) }
  • 40. PITFALLS COMBINING FUTURES • falsch kombiniert /** * scala> Await.result(combined, 11 seconds) * warning: there were 1 feature warning(s); re-run with -feature for details * java.util.concurrent.TimeoutException: Futures timed out after [11 seconds] */ • Richtig kombiniert /** * scala> Await.result(combined, 11 seconds) * warning: there were 1 feature warning(s); re-run with -feature for details * res7: String = Ich brauche zehn Sekunden.Ich brauche fünf Sekunden. */
  • 41. MAPS UND LISTEN : NÜTZLICHE FUNKTIONEN • partition • groupBy
  • 42. PARTITION • Liste aufteilen scala> (1 to 10) partition (_ % 2 == 0) ! res1: ( scala.collection.immutable.IndexedSeq[Int], scala.collection.immutable.IndexedSeq[Int] ) = ( Vector(2, 4, 6, 8, 10), Vector(1, 3, 5, 7, 9) )
  • 43. GROUPBY • Listenelemente gruppieren • Ergebnis ist eine Map scala> (1 to 10) groupBy (identity) res2: scala.collection.immutable.Map[Int,scala.collection.immutable.IndexedSeq[Int]] = Map( 5 -> Vector(5), 1 -> Vector(1), ...) ! scala> (1 to 10) groupBy (_ % 4 == 0) res3: scala.collection.immutable.Map[Boolean,scala.collection.immutable.IndexedSeq[Int]] = Map( false -> Vector(1, 2, 3, 5, 6, 7, 9, 10), true -> Vector(4, 8) ) ! scala> "Lagerregal".toList.groupBy(x => x.toLower) res12: Map( e -> a -> g -> l -> r -> ) scala.collection.immutable.Map[Char,List[Char]] = List(e, List(a, List(g, List(L, List(r, e), a), g), l), r)
  • 44. KOMPOSITION MIT STACKABLE TRAITS
  • 45. SZENARIO • Extraktoren liefern Extractions • Extraktoren existieren in verschiedenen Ausprägungen, z.b. mit Datenbankanbindung • Extraktoren sollen beliebig kombiniert werden können, Kombinationsreihenfolge ist wichtig
  • 46. TYPEN UND BASISTRAITS • Extraction case class Extraction(value: String, src: Strin • Extrations type Extractions = • Extractors Map[String,Extraction] trait Extractors { def process : Extractions }
  • 47. REGELBASIERTE EXTRAKTOREN • Regelbasierter Extraktor, liefter stets die gleichen Werten class RuleExtractors extends Extractors{ ! def process : Extractions = { // Message println("I'm a stupid Rule based Extraction System") ! Map( "name" -> Extraction("Andi","rule"), "amount" -> Extraction("2.50 €", "rule"), "bic" -> Extraction("ABCEDFG","rule") ) } }
  • 48. TEMPLATING EXTRAKTOREN • Template übeschreibt den Wert unter „amount“ trait TemplatingOverwrite extends Extractors{ ! abstract override def process : Extractions = { println("I use mighty templates") super.process ++ Map("amount" -> Extraction("2500 €“,"temlating") ) } ! }
  • 49. EXTRAKTOREN MIT DATENBANK • Schlägt BIC nach • Ersetzt „name“ auf Basis des bicNameMappings trait BICNameDatabase extends Extractors{ ! lazy val oldExtractions = super.process ! val bicNameDB = Map("ABCEDFG" -> "cristof") ! def lookUpExtraction : Option[Extraction] = for { Extraction(bic,_) <- oldExtractions get "bic" name <- bicNameDB get bic } yield Extraction(name,"BICNameStore") ! ! abstract override def process : Extractions = { println("I use a BICStore to find the right names") lookUpExtraction map (ex => oldExtractions ++ Map("name" -> ex) ) getOrElse oldExtractions } ! }
  • 50. STACKABLE TRAITS BESONDERHEITEN • abstract o
  • Related Search
    Related Documents
    View more...
    We Need Your Support
    Thank you for visiting our website and your interest in our free products and services. We are nonprofit website to share and download documents. To the running of this website, we need your help to support us.

    Thanks to everyone for your continued support.

    No, Thanks