├── .gitignore ├── Appendix ├── install_jvm_211_linux.sh ├── install_jvm_211_osx.sh └── install_sbt_osx_linux.sh ├── Chapter01 ├── build.sbt ├── project │ └── build.properties └── src │ └── main │ └── scala │ └── ch01 │ ├── Ch01.scala │ └── Collections213.scala ├── Chapter02 ├── build.sbt ├── project │ └── build.properties └── src │ └── main │ └── scala │ └── ch02 │ ├── Contravariance.scala │ ├── Covariance.scala │ ├── ExistentialTypes.scala │ ├── GeneralisedConstraints.scala │ ├── GeneralisedPhantomTypes.scala │ ├── HigherKindedTypes.scala │ ├── InfixTypes.scala │ ├── Invariance.scala │ ├── Linearization.scala │ ├── PathDependentTypes.scala │ ├── PhantomTypes.scala │ ├── RecursiveTypes.scala │ ├── SelfType.scala │ ├── TypeConstraints.scala │ ├── TypeInference.scala │ ├── TypeLambdas.scala │ ├── TypeMembers.scala │ └── TypeParameters.scala ├── Chapter03 ├── .sbtopts ├── build.sbt ├── project │ └── build.properties └── src │ └── main │ └── scala │ └── ch03 │ ├── Closures.scala │ ├── Currying.scala │ ├── FunctionLiterals.scala │ ├── Functions.scala │ ├── HigherOrder.scala │ ├── Imports.scala │ ├── LoanerPattern.scala │ ├── LocalMethod.scala │ ├── LocalToVal.scala │ ├── MethodDefinition.scala │ ├── ObjectOrientation.scala │ ├── Polymorphism.scala │ └── Recursion.scala ├── Chapter04 ├── .sbtopts ├── build.sbt ├── project │ └── build.properties └── src │ └── main │ ├── java │ └── ch04 │ │ └── RandomInt.java │ └── scala │ └── ch04 │ ├── ContextBounds.scala │ ├── ImplicitArguments.scala │ ├── ImplicitClasses.scala │ ├── ImplicitConversions.scala │ ├── TypeClassVariance.scala │ ├── TypeClasses.scala │ ├── ViewBounds.scala │ ├── package.scala │ ├── resolutionRulesImplicitScope.scala │ └── resolutionRulesLexicalScope.scala ├── Chapter05 ├── build.sbt ├── project │ └── build.properties └── src │ └── main │ └── scala │ ├── Assesments.scala │ ├── Generators.scala │ ├── Properties.scala │ └── Shrinking.scala ├── Chapter06 ├── build.sbt ├── project │ └── build.properties └── src │ └── main │ └── scala │ └── ch06 │ ├── Effects.scala │ ├── EitherEffect.scala │ ├── FutureEffect.scala │ ├── OptionEffect.scala │ └── TryEffect.scala ├── Chapter07 ├── build.sbt ├── project │ └── build.properties └── src │ ├── main │ └── scala │ │ └── ch07 │ │ ├── Assessment.scala │ │ ├── Foldable.scala │ │ ├── Group.scala │ │ ├── Monoid.scala │ │ ├── MonoidFoldable.scala │ │ ├── Reducible.scala │ │ └── Semigroup.scala │ └── test │ └── scala │ └── ch07 │ ├── AssessmentSpecification.scala │ ├── GroupSpecification.scala │ ├── MonoidFoldableSpecification.scala │ ├── MonoidSpecification.scala │ └── SemigroupSpecification.scala ├── Chapter08 ├── build.sbt ├── project │ └── build.properties └── src │ ├── main │ └── scala │ │ └── ch08 │ │ ├── Applicative.scala │ │ ├── Functor.scala │ │ ├── Model.scala │ │ └── Traversable.scala │ └── test │ └── scala │ └── ch08 │ ├── ApplicativeSpecification.scala │ └── FunctorSpecification.scala ├── Chapter09 ├── build.sbt ├── project │ └── build.properties └── src │ ├── main │ └── scala │ │ └── ch09 │ │ ├── Assesments.scala │ │ ├── Boat.scala │ │ ├── Ch09.scala │ │ ├── IdExample.scala │ │ ├── Monad.scala │ │ ├── Reader.scala │ │ ├── State.scala │ │ └── Writer.scala │ └── test │ └── scala │ └── ch09 │ └── MonadSpecification.scala ├── Chapter10 ├── build.sbt ├── project │ └── build.properties └── src │ └── main │ └── scala │ └── ch10 │ ├── Assessments.scala │ ├── Ch10.scala │ ├── FreeMonad.scala │ ├── TransformerStacks.scala │ └── Transofmers.scala ├── Chapter11 ├── build.sbt ├── project │ └── build.properties └── src │ ├── main │ ├── resources │ │ ├── application.conf │ │ └── grocery.conf │ └── scala │ │ └── ch11 │ │ ├── Bakery.scala │ │ └── Store.scala │ └── test │ └── scala │ └── ch11 │ ├── BakerySpec.scala │ └── StoreSpec.scala ├── Chapter12 ├── build.sbt ├── project │ └── build.properties └── src │ ├── main │ ├── resources │ │ ├── application.conf │ │ └── grocery.conf │ └── scala │ │ └── ch12 │ │ ├── Baker.scala │ │ ├── Bakery.scala │ │ ├── Boy.scala │ │ ├── Chef.scala │ │ ├── Cook.scala │ │ ├── Manager.scala │ │ ├── Mixer.scala │ │ ├── Oven.scala │ │ └── Shop.scala │ └── test │ └── scala │ └── ch12 │ ├── BakerySpec.scala │ └── ShopSpec.scala ├── Chapter13 ├── build.sbt ├── project │ └── build.properties └── src │ ├── main │ ├── resources │ │ ├── application.conf │ │ └── grocery.conf │ └── scala │ │ └── ch13 │ │ ├── Bakery.scala │ │ ├── Balancer.scala │ │ └── Store.scala │ └── test │ └── scala │ └── ch13 │ └── BakerySpec.scala ├── Chapter14 ├── akka-http │ └── src │ │ ├── main │ │ ├── resources │ │ │ ├── application.conf │ │ │ ├── db │ │ │ │ └── migration │ │ │ │ │ └── V1__default_table.sql │ │ │ ├── default.conf │ │ │ └── logback.xml │ │ └── scala │ │ │ └── ch14 │ │ │ ├── DB.scala │ │ │ ├── InventoryActor.scala │ │ │ ├── JsonSupport.scala │ │ │ ├── Routes.scala │ │ │ ├── Server.scala │ │ │ ├── config.scala │ │ │ └── model.scala │ │ └── test │ │ ├── resources │ │ └── application.conf │ │ └── scala │ │ └── ch14 │ │ └── RoutesSpec.scala ├── build.sbt ├── ch14.mv.db ├── http4s-doobie │ └── src │ │ ├── it │ │ ├── resources │ │ │ └── test.conf │ │ └── scala │ │ │ └── ServerSpec.scala │ │ └── main │ │ ├── resources │ │ ├── application.conf │ │ └── db_migrations │ │ │ └── V1__inventory_table.sql │ │ └── scala │ │ └── ch14 │ │ ├── DB.scala │ │ ├── Model.scala │ │ ├── Repository.scala │ │ ├── Server.scala │ │ ├── Service.scala │ │ └── config.scala └── project │ ├── build.properties │ └── plugins.sbt ├── Chapter15 ├── .sbtopts ├── baker-api │ └── src │ │ └── main │ │ └── scala │ │ └── ch15 │ │ └── BakerService.scala ├── baker-impl │ └── src │ │ ├── main │ │ ├── resources │ │ │ └── application.conf │ │ └── scala │ │ │ └── ch15 │ │ │ ├── bakerLoader.scala │ │ │ └── bakerServiceImpl.scala │ │ └── test │ │ └── scala │ │ └── ch15 │ │ └── BakerServiceSpec.scala ├── boy-api │ └── src │ │ └── main │ │ └── scala │ │ └── ch15 │ │ ├── BoyService.scala │ │ └── ShopService.scala ├── boy-impl │ └── src │ │ └── main │ │ ├── resources │ │ └── application.conf │ │ └── scala │ │ └── ch15 │ │ ├── BoyServiceImpl.scala │ │ └── boyLoader.scala ├── build.sbt ├── chef-api │ └── src │ │ └── main │ │ └── scala │ │ └── ch15 │ │ └── ChefService.scala ├── chef-impl │ └── src │ │ └── main │ │ ├── resources │ │ └── application.conf │ │ └── scala │ │ └── ch15 │ │ ├── ChefPersistentEntity.scala │ │ ├── ChefServiceImpl.scala │ │ ├── chefLoader.scala │ │ └── chefModel.scala ├── cook-api │ └── src │ │ └── main │ │ └── scala │ │ └── ch15 │ │ └── CookService.scala ├── cook-impl │ └── src │ │ ├── main │ │ ├── resources │ │ │ └── application.conf │ │ └── scala │ │ │ └── ch15 │ │ │ ├── CookServiceImpl.scala │ │ │ └── cookLoader.scala │ │ └── test │ │ └── scala │ │ └── ch15 │ │ └── CookServiceSpec.scala ├── manager-api │ └── src │ │ └── main │ │ └── scala │ │ └── ch15 │ │ └── ManagerService.scala ├── manager-impl │ └── src │ │ └── main │ │ ├── resources │ │ └── application.conf │ │ └── scala │ │ └── ch15 │ │ ├── ManagerServiceImpl.scala │ │ └── managerLoader.scala ├── project │ ├── build.properties │ └── plugins.sbt └── shared-model │ └── src │ └── main │ └── scala │ └── ch15 │ └── model.scala ├── LICENSE ├── README.md └── build.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | target/ 4 | .idea/ 5 | .git/ 6 | **/target 7 | **/.idea 8 | **/target 9 | **/ideatarget 10 | **/.target 11 | **/bin 12 | **/.cache 13 | **/.cache-main 14 | **/.cache-tests 15 | **/.classpath 16 | **/.project 17 | **/.tmpBin 18 | **/.factorypath 19 | **/.settings 20 | **/logs -------------------------------------------------------------------------------- /Appendix/install_jvm_211_linux.sh: -------------------------------------------------------------------------------- 1 | wget https://download.java.net/java/ga/jdk11/openjdk-11_linux-x64_bin.tar.gz 2 | tar -xzf openjdk-11_linux-x64_bin.tar.gz 3 | export JAVA_HOME=`pwd`/jdk-11 4 | echo 'PATH=$PATH':$JAVA_HOME/bin >> ~/.bashrc 5 | export PATH=$PATH:$JAVA_HOME/bin 6 | java -version 7 | -------------------------------------------------------------------------------- /Appendix/install_jvm_211_osx.sh: -------------------------------------------------------------------------------- 1 | wget https://download.java.net/java/ga/jdk11/openjdk-11_osx-x64_bin.tar.gz 2 | tar -xzf openjdk-11_osx-x64_bin.tar.gz 3 | export JAVA_HOME=`pwd`/jdk-11.jdk/Contents/Home 4 | echo 'PATH=$PATH':$JAVA_HOME/bin >> ~/.bashrc 5 | export PATH=$PATH:$JAVA_HOME/bin 6 | java -version 7 | -------------------------------------------------------------------------------- /Appendix/install_sbt_osx_linux.sh: -------------------------------------------------------------------------------- 1 | wget https://piccolo.link/sbt-1.2.3.tgz 2 | tar -xzf sbt-1.2.3.tgz 3 | export SBT_HOME=`pwd`/sbt/bin 4 | echo 'PATH=$PATH':$SBT_HOME >> ~/.bashrc 5 | export PATH=$PATH:$SBT_HOME 6 | sbt version 7 | -------------------------------------------------------------------------------- /Chapter01/build.sbt: -------------------------------------------------------------------------------- 1 | organization := "packt" 2 | version := "1.0-SNAPSHOT" 3 | scalaVersion := "2.13.0" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked") 6 | -------------------------------------------------------------------------------- /Chapter01/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 -------------------------------------------------------------------------------- /Chapter01/src/main/scala/ch01/Ch01.scala: -------------------------------------------------------------------------------- 1 | package ch01 2 | 3 | import java.util.concurrent.atomic.AtomicLong 4 | 5 | import scala.io.StdIn 6 | import scala.util.{Try, Using} 7 | 8 | case class User(name: String, surname: String, email: String) 9 | 10 | object Ch01 extends App { 11 | 12 | "10".toIntOption 13 | "TrUe".toBooleanOption 14 | 15 | val bool = "Not True" 16 | bool.toBooleanOption 17 | 18 | scala.util.Using 19 | 20 | val user = User("John", "Doe", "jd@mail.me") 21 | user.productElementNames.mkString(", ") 22 | user.productElementName(3) 23 | 24 | val tuple = (1, "two", false) 25 | 26 | tuple.productElementNames.mkString(", ") 27 | tuple.productElementName(1) 28 | 29 | def naiveToJsonString(p: Product): String = 30 | (for { i <- 0 until p.productArity } yield 31 | s""""${p.productElementName(i)}": "${p.productElement(i)}"""") 32 | .mkString("{ ", ", ", " }") 33 | 34 | naiveToJsonString(user) 35 | 36 | import scala.util.chaining._ 37 | 38 | import UserDb._ 39 | val userId = 1L 40 | save(update(getById(userId))) 41 | 42 | getById(userId).pipe(update).pipe(save) 43 | 44 | val doEverything = (getById _).andThen(update).andThen(save) 45 | doEverything(userId) 46 | 47 | val lastTick = new AtomicLong(0) 48 | def start(): Unit = lastTick.set(System.currentTimeMillis()) 49 | def measure[A](a: A): Unit = { 50 | val now = System.currentTimeMillis() 51 | val before = lastTick.getAndSet(now) 52 | println(s"$a: ${now - before} ms elapsed") 53 | } 54 | 55 | start() 56 | val result = StdIn.readLine().pipe(_.toIntOption).tap(measure) 57 | val anotherResult = StdIn.readLine().pipe(_.toIntOption).tap(measure) 58 | 59 | final case class Resource(name: String) extends AutoCloseable { 60 | override def close(): Unit = println(s"Closing $name") 61 | def lines = List(s"$name line 1", s"$name line 2") 62 | } 63 | val List(r1, r2, r3) = List("first", "2", "3").map(Resource) 64 | 65 | val lines: Seq[String] = Using.resources(r1, r2, r3) { (u1, u2, u3) => 66 | u1.lines ++ u2.lines ++ u3.lines 67 | } 68 | 69 | println(lines) 70 | 71 | } 72 | 73 | object UserDb { 74 | def getById(id: Long): User = ??? 75 | def update(u: User): User = ??? 76 | def save(u: User): Boolean = ??? 77 | } 78 | -------------------------------------------------------------------------------- /Chapter01/src/main/scala/ch01/Collections213.scala: -------------------------------------------------------------------------------- 1 | package ch01 2 | 3 | import scala.collection.StringView 4 | import scala.collection.SortedSet 5 | 6 | object Collections213 extends App { 7 | 8 | 9 | def transform[C <: Iterable[Char]](i: C): Iterable[Char] = i map { c => 10 | print(s"-$c-") 11 | c.toUpper 12 | } take { 13 | println("\ntake") 14 | 6 15 | } 16 | 17 | val str = "Scala 2.13" 18 | val view: StringView = StringView(str) 19 | 20 | val transformed = transform(view) 21 | val strict = transform(str.toList) 22 | 23 | print("Lazy view constructed: ") 24 | 25 | transformed.foreach(print) // forcing 26 | 27 | print("\nLazy view forced: ") 28 | 29 | println(transformed.to(List)) // also forcing 30 | 31 | println(s"Strict: $strict") 32 | 33 | val set = SortedSet(1,2,3) 34 | val ordered = set.map(math.abs) 35 | val unordered = set.to(Set).map(math.abs) 36 | 37 | 38 | ordered.forall(set) 39 | 40 | val vector = IndexedSeq.fill(2)("A") 41 | } 42 | -------------------------------------------------------------------------------- /Chapter02/build.sbt: -------------------------------------------------------------------------------- 1 | organization := "packt" 2 | version := "1.0-SNAPSHOT" 3 | scalaVersion := "2.13.0" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked") 6 | -------------------------------------------------------------------------------- /Chapter02/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/Contravariance.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | 4 | object Contravariance { 5 | 6 | class Drinker[-T] { 7 | def drink(contents: T): Unit = ??? 8 | } 9 | 10 | sealed trait Glass[Contents] { 11 | def contents: Contents 12 | 13 | def knockBack(drinker: Drinker[Contents]): Unit = drinker.drink(contents) 14 | } 15 | 16 | case class Full[Contents](contents: Contents) extends Glass[Contents] 17 | 18 | class Water(purity: Int) 19 | 20 | class PureWater(purity: Int) extends Water(purity) { 21 | def shine(): Unit = ??? 22 | } 23 | 24 | val glass = Full(new PureWater(100)) 25 | glass.knockBack(new Drinker[PureWater]) 26 | 27 | glass.knockBack(new Drinker[Water]) 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/Covariance.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | 4 | object Covariance { 5 | 6 | sealed trait Glass[+Contents] 7 | 8 | case class Full[Contents](contents: Contents) extends Glass[Contents] 9 | 10 | case object Empty extends Glass[Nothing] 11 | 12 | class Water(purity: Int) 13 | 14 | def drink(glass: Glass[Water]): Unit = ??? 15 | 16 | drink(Full(new Water(100))) 17 | drink(Empty) 18 | } 19 | 20 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/ExistentialTypes.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | 4 | object ExistentialTypes { 5 | 6 | import ch02.Contravariance._ 7 | 8 | def drink[_ <: Water](g: Glass[_]): Unit = { 9 | g.contents; () 10 | } 11 | 12 | import scala.language.existentials 13 | 14 | val glass = Full[T forSome { type T <: Water }](new Water(100)) 15 | } 16 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/GeneralisedConstraints.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | object GeneralisedConstraints { 4 | import Linearization._ 5 | abstract class Wrapper[A] { 6 | val a: A 7 | 8 | // A in flatten shadows A in Wrapper 9 | // def flatten[B, A <: Wrapper[B]]: Wrapper[B] = a 10 | def flatten(implicit ev: A <:< Wrapper[B]): Wrapper[B] = a 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/GeneralisedPhantomTypes.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | object GeneralisedPhantomTypes { 4 | 5 | sealed trait LockState 6 | sealed trait Open extends LockState 7 | sealed trait Closed extends LockState 8 | sealed trait Broken extends LockState 9 | 10 | case class Lock[State <: LockState]() { 11 | def break: Lock[Broken] = Lock() 12 | def open(implicit ev: State =:= Closed): Lock[Open] = Lock() 13 | def close(implicit ev: State =:= Open): Lock[Closed] = Lock() 14 | } 15 | 16 | val openLock = Lock[Open] 17 | 18 | val closedLock = openLock.close 19 | val lock = closedLock.open 20 | val broken = closedLock.break 21 | 22 | // closedLock.close // compile error 23 | // openLock.open // compile error 24 | // broken.open // compile error 25 | } 26 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/HigherKindedTypes.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | 4 | object HigherKindedTypes { 5 | 6 | import scala.language.higherKinds 7 | 8 | sealed trait Container[C] { 9 | def contents: C 10 | } 11 | 12 | case class Glass[C](contents: C) extends Container[C] 13 | 14 | case class Jar[C](contents: C) extends Container[C] 15 | 16 | def fillGlass[C](c: C): Glass[C] = Glass(c) 17 | 18 | def fillJar[C](c: C): Jar[C] = Jar(c) 19 | 20 | sealed trait Filler[CC[_]] { 21 | def fill[C](c: C): CC[C] 22 | } 23 | 24 | object GlassFiller extends Filler[Glass] { 25 | override def fill[C](c: C): Glass[C] = Glass(c) 26 | } 27 | 28 | object JarFiller extends Filler[Jar] { 29 | override def fill[C](c: C): Jar[C] = Jar(c) 30 | } 31 | 32 | def fill[C, G[_]](c: C)(F: Filler[G]): G[C] = F.fill(c) 33 | 34 | val fullGlass: Glass[Int] = fill(100)(GlassFiller) 35 | val fullJar: Jar[Int] = fill(200)(JarFiller) 36 | } 37 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/InfixTypes.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | object InfixTypes { 4 | import Linearization._ 5 | import scala.language.higherKinds 6 | 7 | type Or[A, B] 8 | type And[A, B] 9 | type +=[A, B] = Or[A, B] 10 | type =:[A, B] = And[A, B] 11 | 12 | type CC = Or[And[A, B], C] 13 | type DA = A =: B =: C 14 | type DB = A And B And C 15 | // type E = A += B =: C // wrong associativity 16 | type F = (A += B) =: C 17 | 18 | type |[A, B] = Or[A, B] 19 | type &[A, B] = And[A, B] 20 | type G = A & B | C 21 | } 22 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/Invariance.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | 4 | object Invariance { 5 | 6 | sealed trait Glass[Contents] 7 | 8 | case class Full[Contents](contents: Contents) extends Glass[Contents] 9 | 10 | case object Empty extends Glass[Nothing] 11 | 12 | case class Water(purity: Int) 13 | 14 | def drink(glass: Glass[Water]): Unit = ??? 15 | 16 | drink(Full(Water(100))) 17 | // drink(Empty) 18 | 19 | def drinkAndRefill[B <: Water](glass: Glass[B]): Unit = ??? 20 | 21 | drinkAndRefill(Empty) 22 | } 23 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/Linearization.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | object Linearization { 4 | trait A { 5 | override def toString: String = super.toString + "A" 6 | } 7 | 8 | trait B { 9 | override def toString: String = super.toString + "B" 10 | } 11 | 12 | trait C { 13 | override def toString: String = super.toString + "C" 14 | } 15 | 16 | class E extends A with B with C { 17 | override def toString: String = super.toString + "D" 18 | } 19 | } 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/PathDependentTypes.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | object PathDependentTypes { 4 | 5 | final case class Lock() { 6 | 7 | final case class Key() 8 | 9 | def open(key: Key): Lock = this 10 | 11 | def close(key: Key): Lock = this 12 | 13 | def openWithMaster(key: Lock#Key): Lock = this 14 | 15 | def makeKey: Key = new Key 16 | 17 | def makeMasterKey: Lock#Key = new Key 18 | } 19 | 20 | val blue: Lock = Lock() 21 | val red: Lock = Lock() 22 | val blueKey: blue.Key = blue.makeKey 23 | val anotherBlueKey: blue.Key = blue.makeKey 24 | val redKey: red.Key = red.makeKey 25 | 26 | blue.open(blueKey) 27 | blue.open(anotherBlueKey) 28 | // blue.open(redKey) // compile error 29 | // red.open(blueKey) // compile error 30 | 31 | val masterKey: Lock#Key = red.makeMasterKey 32 | 33 | blue.openWithMaster(masterKey) 34 | red.openWithMaster(masterKey) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/PhantomTypes.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | 4 | object PhantomTypes { 5 | 6 | sealed trait LockState 7 | 8 | sealed trait Open extends LockState 9 | 10 | sealed trait Closed extends LockState 11 | 12 | sealed trait Broken extends LockState 13 | 14 | case class Lock[State <: LockState]() { 15 | def open[_ >: State <: Closed]: Lock[Open] = Lock() 16 | 17 | def close[_ >: State <: Open]: Lock[Closed] = Lock() 18 | 19 | def break: Lock[Broken] = Lock() 20 | } 21 | 22 | val openLock = Lock[Open] 23 | 24 | val closedLock = openLock.close 25 | val broken = closedLock.break 26 | 27 | // closedLock.close() // compile error 28 | // openLock.open() // compile error 29 | // broken.open() // compile error 30 | } 31 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/RecursiveTypes.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | object RecursiveTypes { 4 | 5 | sealed trait Secret[E] 6 | 7 | sealed trait Lock[E <: Lock[E]] { self: E => 8 | def open(key: Secret[E]): E = self 9 | } 10 | 11 | // case class IntLock() extends Lock[Int] // compile error 12 | 13 | case class PadLock() extends Lock[PadLock] 14 | // case class CombinationLock() extends Lock[PadLock] // compile error 15 | 16 | case class CombinationLock() extends Lock[CombinationLock] 17 | 18 | 19 | val unlocked: PadLock = PadLock().open(new Secret[PadLock]{}) 20 | CombinationLock().open(new Secret[CombinationLock]{}) 21 | } 22 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/SelfType.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | 4 | object SelfType { 5 | 6 | trait A { 7 | def a: String 8 | } 9 | 10 | trait B { 11 | def b: String 12 | } 13 | 14 | trait C { 15 | this: A => // override `this` 16 | def c: String = this.a 17 | } 18 | 19 | trait D { 20 | self: A with B => // self is an alias for mixed traits 21 | def d: String = this.a + this.b 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/TypeConstraints.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | 4 | object TypeConstraints { 5 | 6 | trait A 7 | 8 | trait B extends A 9 | 10 | trait C extends B 11 | 12 | trait bounds { 13 | type LOWER >: B 14 | type UPPER <: B 15 | } 16 | 17 | trait boundsA extends bounds { 18 | override type LOWER = A 19 | // override type UPPER = A // compile error 20 | } 21 | 22 | trait boundsB extends bounds { 23 | override type LOWER = B 24 | override type UPPER = B 25 | } 26 | 27 | trait boundsC extends bounds { 28 | // override type LOWER = C // compile error 29 | override type UPPER = C 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/TypeInference.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | 4 | object TypeInference { 5 | 6 | case class C() 7 | 8 | class D() 9 | 10 | case class E() 11 | 12 | trait Foo { 13 | def foo: Int 14 | } 15 | 16 | case class F() extends Foo { 17 | def foo: Int = 0 18 | } 19 | 20 | case class G() extends Foo { 21 | def foo: Int = 0 22 | } 23 | 24 | def intOrBool(i: Int, s: Boolean)(b: Boolean): AnyVal = if (b) i else s 25 | 26 | def intOrString(i: Int, s: String)(b: Boolean): Any = if (b) i else s 27 | 28 | def stringOrC(c: C, s: String)(b: Boolean): java.io.Serializable = 29 | if (b) c else s 30 | 31 | def cOrD(c: C, d: D)(b: Boolean): AnyRef = if (b) c else d 32 | 33 | def cOrE(c: C, e: E)(b: Boolean): Product with Serializable = if (b) c else e 34 | 35 | def fOrG(f: F, g: G)(b: Boolean): Product with Serializable with Foo = 36 | if (b) f else g 37 | } 38 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/TypeLambdas.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | 4 | object TypeLambdas { 5 | 6 | import scala.language.higherKinds 7 | import scala.language.reflectiveCalls 8 | 9 | sealed trait Contents 10 | 11 | case class Water(purity: Int) extends Contents 12 | 13 | case class Whiskey(label: String) extends Contents 14 | 15 | sealed trait Container[C] { 16 | def contents: C 17 | } 18 | 19 | case class Glass[C](contents: C) extends Container[C] 20 | 21 | case class Jar[C](contents: C) extends Container[C] 22 | 23 | sealed trait Filler[C <: Contents, CC <: Container[C]] { 24 | def fill(c: C): CC 25 | } 26 | 27 | type WaterFiller[CC <: Container[Water]] = Filler[Water, CC] 28 | 29 | def fillWithWater[CC <: Container[Water]](container: CC)(filler: WaterFiller[CC]) = ??? 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/TypeMembers.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | 4 | object TypeMembers { 5 | 6 | trait HolderA { 7 | type A 8 | 9 | def a: A 10 | } 11 | 12 | class A extends HolderA { 13 | override type A = Int 14 | 15 | override def a = 10 16 | } 17 | 18 | abstract class HolderBC { 19 | type B 20 | type C <: B 21 | 22 | def b: B 23 | 24 | def c: C 25 | } 26 | 27 | // fails to compile 28 | /* 29 | class BC extends HolderBC { 30 | override def b = "String" 31 | override def c = true 32 | } 33 | */ 34 | 35 | trait HolderDEF { 36 | type D >: Null <: AnyRef 37 | type E <: AnyVal 38 | type F = this.type 39 | 40 | def d: D 41 | 42 | def e: E 43 | 44 | def f: F 45 | } 46 | 47 | class DEF extends HolderDEF { 48 | override type D = String 49 | override type E = Boolean 50 | 51 | // incompatible type 52 | // override type E = String 53 | // override def e = true 54 | 55 | override def d = "" 56 | 57 | override def e = true 58 | 59 | // incompatible type 60 | // override def f: DEF = this 61 | override def f: this.type = this 62 | } 63 | 64 | abstract class HolderGH[G, H] { 65 | type I <: G 66 | type J >: H 67 | 68 | def apply(j: J): I 69 | } 70 | 71 | class GH extends HolderGH[String, Null] { 72 | override type I = Nothing 73 | override type J = String 74 | 75 | override def apply(j: J): I = throw new Exception 76 | } 77 | 78 | trait Rule[In] { 79 | type Out 80 | 81 | def method(in: In): Out 82 | } 83 | 84 | } 85 | 86 | -------------------------------------------------------------------------------- /Chapter02/src/main/scala/ch02/TypeParameters.scala: -------------------------------------------------------------------------------- 1 | package ch02 2 | 3 | import java.io 4 | 5 | 6 | object TypeParameters { 7 | 8 | case class Wrapper[A](content: A) { 9 | def unwrap: A = content 10 | } 11 | 12 | def createWrapper[A](a: A): Wrapper[A] = Wrapper(a) 13 | 14 | type ConcreteWrapper[A] = Wrapper[A] 15 | 16 | val wInt: Wrapper[Int] = createWrapper[Int](10) 17 | val wLong: ConcreteWrapper[Long] = createWrapper(10L) 18 | val int: Int = wInt.unwrap 19 | val long: Long = wLong.unwrap 20 | 21 | import scala.language.higherKinds 22 | 23 | type AbstractWrapper[A] 24 | 25 | case class Abc[A](a: A, b: A, c: A) 26 | 27 | val intA: Abc[Int] = Abc(10, 20, 30) 28 | val longA: Abc[Long] = Abc(10L, 20L, 30L) 29 | val whatA: Abc[AnyVal] = Abc(10, 20, true) 30 | val whatB: Abc[io.Serializable] = Abc("10", "20", Wrapper(10)) 31 | val whatC: Abc[Any] = Abc(10, "20", Wrapper(10)) 32 | 33 | trait Constraints[A <: AnyVal, B >: Null <: AnyRef] { 34 | def a: A 35 | 36 | def b: B 37 | } 38 | 39 | // compile error - type parameter bounds 40 | // case class AB(a: String, b: Int) extends Constraints[String, Int] 41 | 42 | // case class AB(a: Int, b: String) extends Constraints[Int, String] 43 | } 44 | -------------------------------------------------------------------------------- /Chapter03/.sbtopts: -------------------------------------------------------------------------------- 1 | -J-Xms512M 2 | -J-Xmx16G 3 | -J-XX:MaxMetaspaceSize=1024M 4 | -------------------------------------------------------------------------------- /Chapter03/build.sbt: -------------------------------------------------------------------------------- 1 | organization := "packt" 2 | version := "1.0-SNAPSHOT" 3 | scalaVersion := "2.13.0" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked") 6 | -------------------------------------------------------------------------------- /Chapter03/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/Closures.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | object Closures { 4 | def outerA = { 5 | val free = 5 6 | 7 | def innerA = { 8 | val free = 20 9 | 10 | def closure(in: Int) = free + in 11 | 12 | closure(10) 13 | } 14 | 15 | innerA + free 16 | } 17 | 18 | outerA 19 | 20 | def forwardReference(in: Int) = { 21 | // def closure(input: Int) = input + free + in // compile error 22 | 23 | val free = 30 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/Currying.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | object Currying { 4 | def sum(a: Int, b: Int) = a + b 5 | def sumAB(a: Int)(b: Int) = a + b 6 | val sum6 = (a: Int) => (b: Int) => (c: Int) => (d: Int) => (e: Int) => (f: Int) => a + b + c + d+ e + f 7 | val sum6Placeholder = (_: Int) + (_: Int) + (_: Int) + (_: Int) + (_: Int) + (_: Int) 8 | } 9 | -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/FunctionLiterals.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | 4 | object FunctionLiterals { 5 | val hash: (Int, Boolean, String, Long) => Int = (a, b, c, d) => { 6 | val ab = 31 * a.hashCode() + b.hashCode() 7 | val abc = 31 * ab + c.hashCode 8 | 31 * abc + d.hashCode() 9 | } 10 | val hashInferred = (a: Int, b: Boolean, c: String, d: Long) => { 11 | val ab = 31 * a.hashCode() + b.hashCode() 12 | val abc = 31 * ab + c.hashCode 13 | 31 * abc + d.hashCode() 14 | } 15 | 16 | def printHash(hasher: String => Int)(s: String): Unit = println(hasher(s)) 17 | 18 | val hasher1: String => Int = s => s.hashCode 19 | val hasher2 = (s: String) => s.hashCode 20 | printHash(hasher1)("Full") 21 | printHash(hasher2)("Inferred result type") 22 | 23 | printHash((s: String) => s.hashCode)("inline") 24 | printHash((s) => s.hashCode)("inline + inference") 25 | printHash(s => s.hashCode)("single argument parentheses") 26 | printHash(_.hashCode)("placeholder syntax") 27 | 28 | val hashPlaceholder = (_: Int) * 31^4 + (_: Int) * 31^3 + (_: Int) * 31^2 + (_: Int) * 31 29 | } 30 | -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/Functions.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | 4 | object Functions { 5 | def method(name: String) = { 6 | def function(in1: Int, in2: String, in3: Boolean): String = name + in2 7 | function _ 8 | } 9 | val function = method("name") 10 | 11 | def fourParams(one: String, two: Int, three: Boolean, four: Long) = () 12 | val applyTwo = fourParams("one", _: Int, true, _: Long) 13 | } 14 | -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/HigherOrder.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | object HigherOrder { 4 | def printHash(hasher: String => Int)(s: String): Unit = println(hasher(s)) 5 | def printPoly[A](hasher: A => Int)(s: A): Unit = println(hasher(s)) 6 | def printer[A, B, C <: A](a: C)(f: A => B): Unit = println(f(a)) 7 | printPoly((_: String).hashCode)("HaHa") 8 | printer(42)((_: Int) / 2) 9 | printer("HoHo")(_.length) 10 | printer(42)(identity) 11 | } 12 | -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/Imports.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | object Imports { 4 | val next = Math.nextAfter _ 5 | next(10f, 20f) 6 | val /\ = Math.hypot(_, _) 7 | /\(10, 20) 8 | } 9 | -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/LoanerPattern.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | 4 | object LoanerPattern { 5 | class Loan[-T <: AutoCloseable, +R](app: T => R) extends (T => R) { 6 | override def apply(t: T): R = try app(t) finally t.close() 7 | } 8 | new Loan((_: java.io.BufferedReader).readLine())(Console.in) 9 | } 10 | -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/LocalMethod.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | object LocalMethod { 4 | def average(in: Int*): Int = { 5 | def sum(in: Int*): Int = in.sum 6 | 7 | def count(in: Int*): Int = in.size 8 | 9 | sum(in: _*) / count(in: _*) 10 | } 11 | 12 | val items = Seq(1, 2, 3, 4, 5) 13 | val avg = average(items: _*) 14 | 15 | def averageNoPassing(in: Int*): Int = { 16 | def sum: Int = in.sum 17 | 18 | def count: Int = in.size 19 | 20 | sum / count 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/LocalToVal.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | object LocalToVal { 4 | val items = Seq(1, 2, 3, 4, 5) 5 | val avg = { 6 | def sum(in: Int*): Int = in.sum 7 | 8 | def count(in: Int*): Int = in.size 9 | 10 | sum(items: _*) / count(items: _*) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/MethodDefinition.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | object MethodDefinition { 4 | def equal(arg1: String, arg2: Int): Boolean = !nonEqual(arg1, arg2) 5 | 6 | private def nonEqual(arg1: String, arg2: Int) = arg1 != arg2.toString 7 | 8 | def defaultValues(a: String = "default")(b: Int = 0, c: String = a)( 9 | implicit d: Long = b, 10 | e: String = a) = ??? 11 | 12 | def byName(int: => Int) = { 13 | println(int) 14 | println(int) 15 | } 16 | 17 | byName({ 18 | println("Calculating") 19 | 10 * 2 20 | }) 21 | 22 | def variable(a: String, b: Int*): Unit = { 23 | val _: collection.Seq[Int] = b 24 | } 25 | 26 | variable("vararg", 1, 2, 3) 27 | variable("Seq", Seq(1, 2, 3): _*) 28 | 29 | def named(first: Int, second: String, third: Boolean) = 30 | s"$first, $second, $third" 31 | 32 | named(third = false, first = 10, second = "Nice") 33 | named(10, third = true, second = "Cool") 34 | } 35 | -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/ObjectOrientation.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | 4 | object ObjectOrientation { 5 | val A: Function2[Long, Long, Long] = (m, n) => 6 | if (m == 0) n + 1 7 | else if (n == 0) A.apply(m - 1, 1) 8 | else A.apply(m - 1, A.apply(m, n - 1)) 9 | 10 | val objectOrientedA: Function2[Long, Long, Long] = new Function2[Long, Long, Long] { 11 | def apply(m: Long, n: Long): Long = 12 | if (m == 0) n + 1 13 | else if (n == 0) objectOrientedA(m - 1, 1) 14 | else objectOrientedA(m - 1, objectOrientedA(m, n - 1)) 15 | } 16 | 17 | def isPalindrome(s: String): Boolean = { 18 | @scala.annotation.tailrec 19 | def helper(s: String, acc: Boolean):Boolean = { 20 | if (!acc) acc 21 | else if (s.length < 2) true else helper(s.drop(1).dropRight(1), s.head == s.last) 22 | } 23 | helper(s, acc = true) 24 | } 25 | 26 | val doReverse: PartialFunction[String, String] = { 27 | case str if !isPalindrome(str) => str.reverse 28 | } 29 | val noReverse: PartialFunction[String, String] = { 30 | case str if isPalindrome(str) => str 31 | } 32 | def reverse = noReverse orElse doReverse 33 | 34 | 35 | val upper = (_: String).toUpperCase 36 | def fill(c: Char) = c.toString * (_: String).length 37 | def filter(c: Char) = (_: String).filter(_ == c) 38 | 39 | val chain = List(upper, filter('L'), fill('*')) 40 | val allAtOnce = Function.chain(chain) 41 | 42 | val static = upper andThen filter('a') andThen fill('C') 43 | 44 | allAtOnce("List(upper _, filter('a'), fill('C'))") 45 | } 46 | -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/Polymorphism.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | object Polymorphism { 4 | sealed trait Glass[+Contents] 5 | 6 | case class Full[Contents](contents: Contents) extends Glass[Contents] 7 | 8 | case object Empty extends Glass[Nothing] 9 | 10 | case class Water(purity: Int) 11 | 12 | def drink(glass: Glass[Water]): Unit = ??? 13 | 14 | def drinkAndRefill[B](glass: Glass[B]): Glass[B] = ??? 15 | 16 | def drinkAndRefillWater[B >: Water, C >: B](glass: Glass[B]): Glass[C] = glass 17 | 18 | drinkAndRefill[Nothing](Empty) 19 | 20 | def drinkFun[B] = (glass: Glass[B]) => glass 21 | } 22 | -------------------------------------------------------------------------------- /Chapter03/src/main/scala/ch03/Recursion.scala: -------------------------------------------------------------------------------- 1 | package ch03 2 | 3 | object Recursion extends App { 4 | 5 | def reverse(s: String): String = { 6 | if (s.length < 2) s 7 | else reverse(s.tail) + s.head 8 | } 9 | 10 | println(reverse("Recursive function call")) 11 | 12 | def tailRecReverse(s: String): String = { 13 | @scala.annotation.tailrec 14 | def reverse(s: String, acc: String): String = 15 | if (s.length < 2) s + acc 16 | else reverse(s.tail, s"${s.head}$acc") 17 | 18 | reverse(s, "") 19 | } 20 | 21 | def inspectReverse(s: String): String = { 22 | @scala.annotation.tailrec 23 | def reverse(s: String, acc: String): String = 24 | if (s.length < 2) { 25 | new Exception().printStackTrace(); s + acc 26 | } else reverse(s.tail, s"${s.head}$acc") 27 | 28 | reverse(s, "") 29 | } 30 | 31 | println(inspectReverse("Recursive function call")) 32 | 33 | object Hofstadter { 34 | def F(n: Int): Int = if (n == 0) 1 else n - M(F(n - 1)) 35 | 36 | def M(n: Int): Int = if (n == 0) 0 else n - F(M(n - 1)) 37 | } 38 | 39 | println(Hofstadter.F(100)) 40 | 41 | val A: (Long, Long) => Long = (m, n) => 42 | if (m == 0) n + 1 43 | else if (n == 0) A(m - 1, 1) 44 | else A(m - 1, A(m, n - 1)) 45 | 46 | object Trampolined { 47 | import util.control.TailCalls._ 48 | 49 | def tailA(m: BigInt, n: BigInt): TailRec[BigInt] = { 50 | if (m == 0) done(n + 1) 51 | else if (n == 0) tailcall(tailA(m - 1, 1)) 52 | else tailcall(tailA(m, n - 1)).flatMap(tailA(m - 1, _)) 53 | } 54 | def A(m: Int, n: Int): BigInt = tailA(m, n).result 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Chapter04/.sbtopts: -------------------------------------------------------------------------------- 1 | -J-Xms512M 2 | -J-Xmx16G 3 | -J-XX:MaxMetaspaceSize=1024M 4 | -------------------------------------------------------------------------------- /Chapter04/build.sbt: -------------------------------------------------------------------------------- 1 | organization := "packt" 2 | version := "1.0-SNAPSHOT" 3 | scalaVersion := "2.13.0" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked") 6 | -------------------------------------------------------------------------------- /Chapter04/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 -------------------------------------------------------------------------------- /Chapter04/src/main/java/ch04/RandomInt.java: -------------------------------------------------------------------------------- 1 | package ch04; 2 | 3 | import java.util.Random; 4 | 5 | public class RandomInt { 6 | static Integer randomInt() { 7 | return new Random(System.currentTimeMillis()).nextInt(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Chapter04/src/main/scala/ch04/ContextBounds.scala: -------------------------------------------------------------------------------- 1 | package ch04 2 | 3 | object ContextBounds { 4 | 5 | trait CanEqual[T] { def hash(t: T): Int } 6 | 7 | implicit val stringEqual: CanEqual[String] = new CanEqual[String] { 8 | def hash(in: String): Int = in.hashCode() 9 | } 10 | 11 | implicit val intEqual: CanEqual[Int] = identity _ 12 | 13 | def equal[CA, CB](a: CA, b: CB) 14 | (implicit ca: CanEqual[CA], cb: CanEqual[CB]): Boolean = 15 | ca.hash(a) == cb.hash(b) 16 | 17 | def equalBounds[CA: CanEqual, CB: CanEqual](a: CA, b: CB): Boolean = { 18 | val hashA = implicitly[CanEqual[CA]].hash(a) 19 | val hashB = implicitly[CanEqual[CB]].hash(b) 20 | hashA == hashB 21 | } 22 | 23 | def equalDelegate[CA: CanEqual, CB: CanEqual](a: CA, b: CB): Boolean = equal(a, b) 24 | 25 | equal(10, 20) 26 | equalBounds("10", "20") 27 | equalDelegate(1598, "20") 28 | } 29 | -------------------------------------------------------------------------------- /Chapter04/src/main/scala/ch04/ImplicitArguments.scala: -------------------------------------------------------------------------------- 1 | package ch04 2 | 3 | object ImplicitArguments { 4 | 5 | case class A[T](a: T) 6 | case class B[T](a: T) 7 | 8 | def ab[C](name: String)(a: A[C])(implicit b: B[C]): Unit = println(s"$name$a$b") 9 | 10 | // ab("1")(A("A")) // fails to compile 11 | 12 | implicit val b: B[String] = B("[Implicit]") 13 | 14 | ab("1")(A("A")) 15 | 16 | // implicit val c: B[String] = B("[Another Implicit]") 17 | 18 | // ab("1")(A("A")) // fails to compile 19 | 20 | ab("1")(A("A"))(b) 21 | // ab("1")(A("A"))(c) 22 | 23 | def withTimestamp(s: String)(implicit time: Long): Unit = println(s"$time: $s") 24 | 25 | object scope1 { 26 | implicit def randomLong: Long = scala.util.Random.nextLong() 27 | 28 | withTimestamp("First") 29 | withTimestamp("Second") 30 | } 31 | 32 | object scope2 { 33 | implicit def recursiveLong(implicit seed: Long): Long = scala.util.Random.nextLong(seed) 34 | 35 | // withTimestamp("Third") // fails to compile 36 | } 37 | 38 | 39 | object Application { 40 | case class Configuration(name: String) 41 | implicit val cfg: Configuration = Configuration("test") 42 | class Persistence(implicit cfg: Configuration) { 43 | class Database(implicit cfg: Configuration) { 44 | def query(id: Long)(implicit cfg: Configuration) = ??? 45 | def update(id: Long, name: String)(implicit cfg: Configuration) = ??? 46 | } 47 | new Database().query(1L) 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Chapter04/src/main/scala/ch04/ImplicitClasses.scala: -------------------------------------------------------------------------------- 1 | package ch04 2 | 3 | 4 | object ImplicitClasses { 5 | 6 | case class A[T](a: T) { def doA(): T = a } 7 | 8 | case class B[T](b: T) { def doB(): T = b } 9 | 10 | import scala.language.implicitConversions 11 | implicit def a2b[T](a: A[T]): B[T] = B(a.a) 12 | 13 | A("I'm an A").doB() 14 | 15 | 16 | implicit class C[T](a: A[T]) { def doC(): T = a.a } 17 | 18 | A("I'm an A").doC() 19 | } 20 | -------------------------------------------------------------------------------- /Chapter04/src/main/scala/ch04/ImplicitConversions.scala: -------------------------------------------------------------------------------- 1 | package ch04 2 | 3 | object ImplicitConversions { 4 | import scala.language.implicitConversions 5 | 6 | // defined in predef 7 | // implicit def int2Integer(x: Int): java.lang.Integer = x.asInstanceOf[java.lang.Integer] 8 | // implicit def Integer2int(x: java.lang.Integer): Int = x.asInstanceOf[Int] 9 | 10 | val integer: Integer = RandomInt.randomInt() 11 | val int: Int = math.abs(integer) 12 | 13 | // @inline implicit def augmentString(x: String): StringOps = new StringOps(x) 14 | 15 | "I'm a string".flatMap(_.toString * 2) ++ ", look what I can" 16 | 17 | case class A[T](a: T) 18 | case class B[T](a: T) 19 | 20 | implicit def t2A[T](a: T): A[T] = A(a) 21 | implicit def t2B[T](a: T): B[T] = B(a) 22 | 23 | def ab[C](a: B[A[C]]): Unit = println(a) 24 | 25 | ab(A("A")) 26 | 27 | // ab("A") fails 28 | 29 | ab("A": A[String]) 30 | 31 | implicit val directions: List[String] = List("North", "West", "South", "East") 32 | implicit val grades: Map[Char, String] = 33 | Map('A' -> "90%", 'B' -> "80%", 'C' -> "70%", 'D' -> "60%", 'F' -> "0%") 34 | 35 | println("B" + 42: String) 36 | println("B" + (42: String)) 37 | "B" + 'C' 38 | "B" + ('C': String) 39 | "B" + (2: String) 40 | } 41 | -------------------------------------------------------------------------------- /Chapter04/src/main/scala/ch04/TypeClassVariance.scala: -------------------------------------------------------------------------------- 1 | package ch04 2 | 3 | object TypeClassVariance { 4 | 5 | trait Cable[C] { 6 | def connect(c: C): Boolean 7 | } 8 | 9 | abstract class UsbConnector 10 | case class Usb(orientation: Boolean) extends UsbConnector 11 | case class Lightning(length: Int) extends UsbConnector 12 | case class UsbC[Kind](kind: Kind) extends UsbConnector 13 | 14 | implicit val usbCable: Cable[UsbConnector] = new Cable[UsbConnector] { 15 | override def connect(c: UsbConnector): Boolean = { 16 | println(s"Connecting $c") 17 | true 18 | } 19 | } 20 | 21 | implicit def usbPolyCable[T <: UsbConnector]: Cable[T] = new Cable[T] { 22 | override def connect(c: T): Boolean = { 23 | println(s"Poly-Connecting $c") 24 | true 25 | } 26 | } 27 | 28 | implicit val usbCCable: Cable[UsbC[String]] = new Cable[UsbC[String]] { 29 | override def connect(c: UsbC[String]): Boolean = { 30 | println(s"Connecting USB C ${c.kind}") 31 | true 32 | } 33 | } 34 | 35 | def connectCable[C : Cable](c: C): Unit = implicitly[Cable[C]].connect(c) 36 | 37 | connectCable(UsbC("3.1")) 38 | 39 | // implicitly[Cable[UsbConnector] <:< Cable[UsbC[String]]] 40 | } 41 | 42 | 43 | 44 | 45 | 46 | /* 47 | 48 | 49 | 50 | sealed trait Mode 51 | case class Usb2(version: String) extends Mode 52 | case class Usb3(version: String) extends Mode 53 | case object PowerDelivery extends Mode 54 | case object Alternate extends Mode 55 | case object AudioAdapterAccessory extends Mode 56 | 57 | class UsbC[M <: Mode](val mode: M) 58 | 59 | class UsbC3(override val mode: Usb3) extends UsbC[Usb3](mode) 60 | 61 | 62 | implicit def usbC[M <: Mode]: Cable[UsbC[M]] = (_: UsbC[M]).mode.non 63 | 64 | def connectCable[C: Cable](c: C): Boolean = implicitly[Cable[C]].connect(c) 65 | */ 66 | -------------------------------------------------------------------------------- /Chapter04/src/main/scala/ch04/TypeClasses.scala: -------------------------------------------------------------------------------- 1 | package ch04 2 | 3 | object TypeClasses { 4 | 5 | object OO { 6 | trait Cable { 7 | def connect(): Boolean 8 | } 9 | case class Usb(orientation: Boolean) extends Cable { 10 | override def connect(): Boolean = orientation 11 | } 12 | case class Lightning(length: Int) extends Cable { 13 | override def connect(): Boolean = length > 100 14 | } 15 | case class UsbC(kind: String) extends Cable { 16 | override def connect(): Boolean = kind.contains("USB 3.1") 17 | } 18 | def connectCable(c: Cable): Boolean = c.connect() 19 | } 20 | 21 | OO.connectCable(OO.Usb(false)) 22 | OO.connectCable(OO.Lightning(150)) 23 | 24 | object TC { 25 | 26 | case class Usb(orientation: Boolean) 27 | case class Lightning(length: Int) 28 | case class UsbC[Kind](kind: Kind) 29 | 30 | @scala.annotation.implicitNotFound("Cannot connect cable of type ${C}") 31 | trait Cable[C] { 32 | def connect(c: C): Boolean 33 | } 34 | implicit val UsbCable: Cable[Usb] = new Cable[Usb] { 35 | override def connect(c: Usb): Boolean = c.orientation 36 | } 37 | implicit val LightningCable: Cable[Lightning] = (_: Lightning).length > 100 38 | 39 | // compile error 40 | // implicit val UsbCCable: Cable[UsbC] = (c: UsbC) => c.kind.contains("USB 3.1") 41 | 42 | implicit val UsbCCableString: Cable[UsbC[String]] = 43 | (_: UsbC[String]).kind.contains("USB 3.1") 44 | 45 | def connectCable[C: Cable](c: C): Boolean = implicitly[Cable[C]].connect(c) 46 | 47 | import scala.language.reflectiveCalls 48 | 49 | implicit def usbCCableDelegate[T]( 50 | implicit conn: T => Boolean 51 | ): Cable[UsbC[T]] = 52 | (c: UsbC[T]) => conn(c.kind) 53 | 54 | implicit val symbolConnect: Symbol => Boolean = 55 | (_: Symbol).name.toLowerCase.contains("cable") 56 | 57 | implicit def adapt[A: Cable, B: Cable]: Cable[(A, B)] = 58 | (ab: (A, B)) => 59 | implicitly[Cable[A]].connect(ab._1) && implicitly[Cable[B]] 60 | .connect(ab._2) 61 | 62 | } 63 | 64 | import ch04.TypeClasses.TC._ 65 | connectCable(Usb(false)) 66 | connectCable(Lightning(150)) 67 | connectCable(UsbC("USB 3.1")) 68 | connectCable(UsbC(Symbol("NonameCable"))) 69 | connectCable(UsbC(Symbol("FakeKable"))) 70 | 71 | /* 72 | Bad design, bad! 73 | 74 | implicit val isEven: Int => Boolean = i => i % 2 == 0 75 | implicit val hexChar: Char => Boolean = c => c >= 'A' && c <='F' 76 | 77 | connectCable(UsbC(10)) 78 | connectCable(UsbC(11)) 79 | connectCable(UsbC(Symbol("D"))) 80 | 81 | */ 82 | 83 | val usb2usbC = (Usb(false), UsbC(Symbol("NonameCable"))) 84 | connectCable(usb2usbC) 85 | val lightning2usbC = (Lightning(150), UsbC(Symbol("NonameCable"))) 86 | connectCable(lightning2usbC) 87 | 88 | val usbC2usb2lightning2usbC = ( 89 | ( 90 | (UsbC(Symbol("NonameCable")), Usb(false)), 91 | (Lightning(150), UsbC("USB 3.1")) 92 | ) 93 | ) 94 | connectCable(usbC2usb2lightning2usbC) 95 | 96 | val noUsbC_Long_Cable = 97 | (UsbC(Symbol("NonameCable")), (Lightning(150), UsbC(10L))) 98 | // connectCable(noUsbC_Long_Cable) // fails to compile 99 | 100 | } 101 | -------------------------------------------------------------------------------- /Chapter04/src/main/scala/ch04/ViewBounds.scala: -------------------------------------------------------------------------------- 1 | package ch04 2 | 3 | object ViewBounds { 4 | 5 | case class CanEqual(hash: Int) 6 | 7 | def equal[CA, CB](a: CA, b: CB)(implicit ca: CA => CanEqual, cb: CB => CanEqual): Boolean = 8 | ca(a).hash == ca(a).hash 9 | 10 | def equalsWithBounds[CA <% CanEqual, CB <% CanEqual](a: CA, b: CB): Boolean = { 11 | val hashA = implicitly[CA => CanEqual].apply(a).hash 12 | val hashB = implicitly[CB => CanEqual].apply(b).hash 13 | hashA == hashB 14 | } 15 | 16 | def equalsWithPassing[CA <% CanEqual, CB <% CanEqual](a: CA, b: CB): Boolean = 17 | equal(a, b) 18 | 19 | import scala.language.implicitConversions 20 | 21 | implicit def str2CanEqual(s: String): CanEqual = CanEqual(s.hashCode) 22 | 23 | implicit class IntCanEqual(i: Int) extends CanEqual(i) 24 | 25 | def test: Boolean = equalsWithPassing("a", "b") 26 | 27 | def test1: Boolean = equalsWithBounds("a", 20) 28 | 29 | def test2: Boolean = equalsWithBounds("a", 97) 30 | } 31 | 32 | /* 33 | ViewBounds.test 34 | ViewBounds.test1 35 | ViewBounds.test2*/ 36 | -------------------------------------------------------------------------------- /Chapter04/src/main/scala/ch04/package.scala: -------------------------------------------------------------------------------- 1 | package object ch04 { 2 | implicit val one: Int = 1 3 | } 4 | -------------------------------------------------------------------------------- /Chapter04/src/main/scala/ch04/resolutionRulesImplicitScope.scala: -------------------------------------------------------------------------------- 1 | package ch04 2 | 3 | import scala.language.implicitConversions 4 | 5 | trait ParentA { def name: String } 6 | trait ParentB 7 | class ChildA(val name: String) extends ParentA with ParentB 8 | 9 | object ParentB { 10 | implicit def a2Char(a: ParentA): Char = a.name.head 11 | 12 | } 13 | object ParentA { 14 | implicit def a2Int(a: ParentA): Int = a.hashCode() 15 | implicit val ordering = new Ordering[ChildA] { 16 | override def compare(a: ChildA, b: ChildA): Int = 17 | implicitly[Ordering[String]].compare(a.name, b.name) 18 | } 19 | } 20 | object ChildA { 21 | implicit def a2String(a: ParentA): String = a.name 22 | } 23 | 24 | trait Test { 25 | def test(a: ChildA) = { 26 | val b: Int = a // companion object of ParentA 27 | // val b: String = a // companion object of ChildA 28 | // val b: Char = a // companion object of ParentB 29 | } 30 | def constructor[T: Ordering](in: T*): List[T] = in.toList.sorted // companion object of type constructor 31 | constructor(new ChildA("A"), new ChildA("B")).sorted // companion object of type parameters 32 | } 33 | -------------------------------------------------------------------------------- /Chapter04/src/main/scala/ch04/resolutionRulesLexicalScope.scala: -------------------------------------------------------------------------------- 1 | package ch04 2 | 3 | package object resolution { 4 | implicit val a: TS = new TS("val in package object") // (1) 5 | } 6 | 7 | package resolution { 8 | class TS(override val toString: String) 9 | class Parent { 10 | // implicit val c: TS = new TS("val in parent class") // (2) 11 | } 12 | trait Mixin { 13 | // implicit val d: TS = new TS("val in mixin") // (3) 14 | } 15 | // import Outer._ // (4) 16 | class Outer { 17 | // implicit val e: TS = new TS("val in outer class") // (5) 18 | // import Inner._ // (6) 19 | 20 | class Inner(/*implicit (7) */ val arg: TS = implicitly[TS]) extends Parent with Mixin { 21 | // implicit val f: TS = new TS("val in inner class") (8) 22 | private val resolve = implicitly[TS] 23 | } 24 | object Inner { 25 | implicit val g: TS = new TS("val in companion object") 26 | } 27 | } 28 | object Outer { 29 | implicit val h: TS = new TS("val in parent companion object") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Chapter05/build.sbt: -------------------------------------------------------------------------------- 1 | organization := "packt" 2 | version := "1.0-SNAPSHOT" 3 | scalaVersion := "2.13.0" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked") 6 | 7 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" withSources () withJavadoc () 8 | -------------------------------------------------------------------------------- /Chapter05/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /Chapter05/src/main/scala/Assesments.scala: -------------------------------------------------------------------------------- 1 | import org.scalacheck.{Arbitrary, Gen, Prop} 2 | import org.scalacheck.Prop.forAll 3 | 4 | object Assesments extends App { 5 | def invariant[T: Ordering: Arbitrary]: Prop = 6 | forAll((l: List[T]) => l.sorted.length == l.length) 7 | 8 | invariant[Long].check 9 | invariant[String].check 10 | 11 | def idempotent[T: Ordering: Arbitrary]: Prop = 12 | forAll((l: List[T]) => l.sorted.sorted == l.sorted) 13 | 14 | idempotent[Long].check 15 | idempotent[String].check 16 | 17 | def inductive[T: Ordering: Arbitrary]: Prop = { 18 | def ordered(l: List[T]): Boolean = 19 | (l.length < 2) || 20 | (ordered(l.tail) && implicitly[Ordering[T]].lteq(l.head, l.tail.head)) 21 | forAll((l: List[T]) => ordered(l.sorted)) 22 | } 23 | 24 | inductive[Int].check 25 | inductive[String].check 26 | 27 | 28 | val genListListInt = Gen.listOf(Gen.listOf(Gen.posNum[Int])) 29 | genListListInt.sample 30 | 31 | val pairGen = for { 32 | uuid <- Gen.uuid 33 | function0 <- Gen.function0(Gen.asciiStr) 34 | } yield (uuid, function0) 35 | 36 | val mapGen = Gen.mapOf(pairGen) 37 | } 38 | -------------------------------------------------------------------------------- /Chapter05/src/main/scala/Generators.scala: -------------------------------------------------------------------------------- 1 | import org.scalacheck.Prop._ 2 | import org.scalacheck.{Arbitrary, Gen, Prop} 3 | 4 | object Generators { 5 | 6 | val morgans: Prop = forAll { (a: Boolean, b: Boolean) => 7 | collect(s"$a $b")(!(a & b) == (!a | !b)) 8 | } 9 | morgans.check 10 | 11 | 12 | def literalGen[T <: Singleton](t: T): Gen[T] = Gen.const(t) 13 | 14 | implicit val myGen: Arbitrary[42] = Arbitrary(literalGen(42)) 15 | 16 | val literalProp: Prop = forAll((_: 42) == 42) 17 | 18 | literalProp.check 19 | 20 | sealed trait Rank 21 | case class SymRank(s: Char) extends Rank { 22 | override def toString: String = s.toString 23 | } 24 | case class NumRank(n: Int) extends Rank { 25 | override def toString: String = n.toString 26 | } 27 | case class Card(suit: Char, rank: Rank) { 28 | override def toString: String = s"$suit $rank" 29 | } 30 | 31 | val suits: Gen[Char] = Gen.oneOf('♡', '♢', '♤', '♧') 32 | val numbers: Gen[NumRank] = Gen.choose(2, 10).map(NumRank) 33 | val symbols: Gen[SymRank] = Gen.oneOf('A', 'K', 'Q', 'J').map(SymRank) 34 | 35 | val full: Gen[Card] = for { 36 | suit <- suits 37 | rank <- Gen.frequency((9, numbers), (4, symbols)) 38 | } yield Card(suit, rank) 39 | 40 | val piquetBad: Gen[Card] = full.suchThat { 41 | case Card(_, _: SymRank) => true 42 | case Card(_, NumRank(n)) => n > 5 43 | } 44 | 45 | 46 | forAll(full) { card => 47 | Prop.collect(card)(true) 48 | }.check 49 | 50 | val piquetNumbers: Gen[NumRank] = Gen.choose(6, 10).map(NumRank) 51 | 52 | val piquet: Gen[Card] = for { 53 | suit <- suits 54 | rank <- Gen.frequency((5, piquetNumbers), (4, symbols)) 55 | } yield Card(suit, rank) 56 | 57 | forAll(piquet) { card => 58 | Prop.collect(card)(true) 59 | }.check 60 | 61 | 62 | val handOfCards1: Gen[List[Card]] = Gen.listOfN(6, piquet) 63 | 64 | val handOfCards: Gen[Set[Card]] = Gen.containerOfN[Set, Card](6, piquet).retryUntil(_.size == 6) 65 | 66 | forAll(handOfCards) { hand => 67 | Prop.collect(hand.mkString(","))(true) 68 | }.check 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Chapter05/src/main/scala/Properties.scala: -------------------------------------------------------------------------------- 1 | import org.scalacheck._ 2 | import org.scalacheck.Prop._ 3 | 4 | object Properties { 5 | 6 | object Introduction { 7 | val stringLengthProp: Prop = forAll { (_: String).length >= 0 } 8 | 9 | stringLengthProp.check 10 | } 11 | 12 | object Commutativity { 13 | forAll((a: Int, b: Int) => a + b == b + a).check 14 | 15 | forAll((a: Int, b: Int) => a * b == b * a).check 16 | 17 | forAll((a: String, b: String) => a + b == b + a).check 18 | 19 | forAll((a: String) => a + "" == "" + a).check 20 | } 21 | 22 | object Associativity { 23 | forAll((a: Int, b: Int, c: Int) => (a + b) + c == a + (b + c)).check 24 | 25 | forAll((a: Int, b: Int, c: Int) => (a * b) * c == a * (b * c)).check 26 | 27 | forAll((a: String, b: String, c: String) => (a + b) + c == a + (b + c)).check 28 | } 29 | 30 | object Identity { 31 | forAll((a: Int) => a + 0 == a && 0 + a == a).check 32 | 33 | forAll((a: Int) => a * 1 == a && 1 * a == a).check 34 | 35 | forAll((a: String) => a + "" == a && "" + a == a).check 36 | } 37 | 38 | object Invariants { 39 | forAll((a: String) => a.toUpperCase().length == a.length).check 40 | forAll((a: String) => a.toSeq.sorted.unwrap.length == a.length).check 41 | forAll(Gen.asciiStr)((a: String) => a.toUpperCase().length == a.length).check 42 | } 43 | 44 | object Idempotency { 45 | forAll((a: String) => a.toUpperCase().toUpperCase() == a.toUpperCase()).check 46 | forAll((a: String) => a.toSeq.sorted.sorted.unwrap == a.toSeq.sorted.unwrap).check 47 | forAll((a: Int) => a * 0 * 0 == a * 0).check 48 | } 49 | 50 | object Induction { 51 | def factorial(n: Long): Long = if (n < 2) n else n * factorial(n - 1) 52 | forAll((a: Byte) => a > 2 ==> (factorial(a) == a * factorial(a - 1))).check 53 | } 54 | 55 | object Symmetry { 56 | forAll((a: String) => a.reverse.reverse == a).check 57 | forAll((a: Int, b: Int) => a + b - b == a).check 58 | } 59 | 60 | object TestOracle { 61 | forAll { a: String => 62 | val chars = a.toCharArray 63 | java.util.Arrays.sort(chars) 64 | val b = String.valueOf(chars) 65 | a.toSeq.sorted.unwrap == b 66 | }.check 67 | } 68 | 69 | object Checking { 70 | private val prop = forAll { a: String => 71 | a.reverse.reverse == a 72 | } 73 | private val timed = within(10000)(prop) 74 | Test.check(timed) { 75 | _.withMinSuccessfulTests(100000).withWorkers(4).withMaxDiscardRatio(3) 76 | } 77 | 78 | forAll { a: String => 79 | classify(a.isEmpty, "empty string", "non-empty string") { 80 | val result = a.toSeq.sorted.unwrap.length == a.length 81 | result 82 | } 83 | }.check() 84 | 85 | val prop2 = "Division by zero" |: protect(forAll((a: Int) => a / a ?= 1)) 86 | prop2.check() 87 | } 88 | 89 | object Combining { 90 | forAll { (a: Int, b: Int, c: Int, d: String) => 91 | val multiplicationLaws = all( 92 | "Commutativity" |: (a * b ?= b * a), 93 | "Associativity" |: ((a * b) * c ?= a * (b * c)), 94 | "Identity" |: all(a * 1 ?= a, 1 * a ?= a) 95 | ) :| "Multiplication laws" 96 | val stringProps = atLeastOne(d.isEmpty, d.nonEmpty) 97 | all(multiplicationLaws, stringProps) 98 | }.check() 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /Chapter05/src/main/scala/Shrinking.scala: -------------------------------------------------------------------------------- 1 | import org.scalacheck.{Arbitrary, Gen, Shrink} 2 | import org.scalacheck.Prop._ 3 | 4 | object Shrinking { 5 | 6 | forAll { (_: Int) < 42 }.check 7 | 8 | forAll(Gen.listOfN(1000, Arbitrary.arbString.arbitrary)) { 9 | _.forall(_.length < 10) 10 | }.check 11 | 12 | 13 | val intShrink: Shrink[Int] = implicitly[Shrink[Int]] 14 | 15 | intShrink.shrink(2008612603).toList 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Chapter06/build.sbt: -------------------------------------------------------------------------------- 1 | organization := "packt" 2 | version := "1.0-SNAPSHOT" 3 | scalaVersion := "2.13.0" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked") 6 | -------------------------------------------------------------------------------- /Chapter06/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /Chapter06/src/main/scala/ch06/Effects.scala: -------------------------------------------------------------------------------- 1 | package ch06 2 | 3 | object Effects extends App { 4 | 5 | type Bait 6 | type Line 7 | type Fish 8 | 9 | import java.util 10 | 11 | val map = new util.HashMap[String, Int] { 12 | put("zero", 0) 13 | } 14 | 15 | val list = new util.ArrayList[String] { 16 | add("zero") 17 | } 18 | 19 | println(map.get("zero")) 20 | println(list.get(0)) 21 | 22 | println(map.get("one")) 23 | 24 | list.get(1) match { 25 | case null => println("Not found") 26 | case notNull => println(notNull) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Chapter06/src/main/scala/ch06/EitherEffect.scala: -------------------------------------------------------------------------------- 1 | package ch06 2 | 3 | trait EitherEffect { 4 | 5 | type OldFormat 6 | type NewFormat 7 | 8 | def runSimulation(): Either[OldFormat, NewFormat] 9 | 10 | type Money 11 | def runPowerPlant(): Either[Long, Money] 12 | 13 | 14 | val right = Right(10) 15 | val left: Either[String, Int] = Left[String, Int]("I'm left") 16 | 17 | def takeOne(l: Either[String, Int]): Unit 18 | 19 | takeOne(left) 20 | 21 | val i = 100 22 | 23 | val either: Either[String, Int] = Either.cond(i > 10, i, "i is greater then 10") 24 | 25 | val right1 = Right(10) 26 | right1.withLeft[String] 27 | 28 | val left1: Either[String, BigDecimal] = Left("HoHoHo").withRight[BigDecimal] 29 | 30 | if (either.isRight) println("Got right") 31 | if (either.isLeft) println("Got left") 32 | 33 | either match { 34 | case Left(value) => println(s"Got Left value $value") 35 | case Right(value) => println(s"Got Right value $value") 36 | } 37 | 38 | 39 | if (either.contains("boo")) println("Is Right and contains 'boo'") 40 | if (either.exists(_ > 10)) println("Is Right and > 10") 41 | if (either.forall(_ > 10)) println("Is Left or > 10") 42 | 43 | 44 | either.getOrElse("Default value for the left side") 45 | 46 | def either(i: Int): Boolean = Either.cond(i > 10, i * 10, new IllegalArgumentException("Give me more")).forall(_ < 100) 47 | } 48 | 49 | trait FishingEitherExample { 50 | import Effects._ 51 | 52 | trait plain { 53 | val buyBait: String => Bait 54 | val makeBait: String => Bait 55 | val castLine: Bait => Line 56 | val hookFish: Line => Fish 57 | def goFishing(bestBaitForFishOrCurse: Either[String, String]): Either[String, Fish] = 58 | bestBaitForFishOrCurse.map(buyBait).map(castLine).map(hookFish) 59 | } 60 | 61 | trait flat { 62 | val buyBait: String => Either[String, Bait] 63 | val makeBait: String => Either[String, Bait] 64 | val castLine: Bait => Either[String, Line] 65 | val hookFish: Line => Either[String, Fish] 66 | 67 | def goFishing(bestBaitForFishOrCurse: Either[String, String]): Either[String, Fish] = for { 68 | baitName <- bestBaitForFishOrCurse 69 | bait <- buyBait(baitName).fold(_ => makeBait(baitName), Right(_)) 70 | line <- castLine(bait) 71 | fish <- hookFish(line) 72 | } yield fish 73 | 74 | } 75 | 76 | } 77 | 78 | -------------------------------------------------------------------------------- /Chapter06/src/main/scala/ch06/FutureEffect.scala: -------------------------------------------------------------------------------- 1 | package ch06 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | import ExecutionContext.Implicits.global 5 | import scala.util.{Failure, Success, Try} 6 | 7 | trait FutureEffect { 8 | val runningForever = Future { 9 | while (true) Thread.sleep(1000) 10 | } 11 | val stringFuture = Future.successful("Well") 12 | val failure = Future.failed(new IllegalArgumentException) 13 | val fromTry = Future.fromTry(Try(10 / 0)) 14 | 15 | val runningLong = Future.unit.map { _ => 16 | while (math.random() > 0.001) Thread.sleep(100) 17 | } 18 | 19 | runningLong.foreach(_ => println("First callback")) 20 | runningLong.foreach(_ => println("Second callback")) 21 | 22 | runningLong.onComplete { 23 | case Success(value) => println(s"Success with $value") 24 | case Failure(ex) => println(s"Failure with $ex") 25 | } 26 | 27 | stringFuture.transform(_.length, ex => new Exception(ex)) 28 | stringFuture.transform { 29 | case Success(value) => Success(value.length) 30 | case Failure(ex) => Failure(new Exception(ex)) 31 | } 32 | 33 | stringFuture.filter(_.length > 10) 34 | 35 | stringFuture.collect { 36 | case s if s.length > 10 => s.toUpperCase 37 | } 38 | 39 | if (runningLong.isCompleted) runningLong.value 40 | 41 | runningForever.value match { 42 | case Some(Success(value)) => println(s"Finished successfully with $value") 43 | case Some(Failure(exception)) => println(s"Failed with $exception") 44 | case None => println("Still running") 45 | } 46 | 47 | } 48 | 49 | trait FishingFutureExample { 50 | import Effects._ 51 | 52 | trait plain { 53 | val buyBait: String => Bait 54 | val makeBait: String => Bait 55 | val castLine: Bait => Line 56 | val hookFish: Line => Fish 57 | def goFishing(bestBaitForFish: Future[String]): Future[Fish] = 58 | bestBaitForFish.map(buyBait).map(castLine).map(hookFish) 59 | } 60 | 61 | trait flat { 62 | val buyBait: String => Future[Bait] 63 | val makeBait: String => Future[Bait] 64 | val castLine: Bait => Future[Line] 65 | val hookFish: Line => Future[Fish] 66 | 67 | def goFishing(bestBaitForFish: Future[String]): Future[Fish] = for { 68 | baitName <- bestBaitForFish 69 | bait <- buyBait(baitName).fallbackTo(makeBait(baitName)) 70 | line <- castLine(bait) 71 | fish <- hookFish(line) 72 | } yield fish 73 | 74 | } 75 | 76 | } 77 | 78 | -------------------------------------------------------------------------------- /Chapter06/src/main/scala/ch06/OptionEffect.scala: -------------------------------------------------------------------------------- 1 | package ch06 2 | 3 | object OptionEffect extends App { 4 | val opt0 = None 5 | val opt: Option[Int] = Some(10) 6 | val opt2 = Some("I'm a non-empty option") 7 | val opt3 = Some(null) 8 | 9 | def opt4[A](a: A): Option[A] = if (a == null) None else Some(a) 10 | 11 | def opt5[A](a: A): Option[A] = Option(a) 12 | 13 | if (opt.isDefined) println(opt.get) 14 | if (opt.isEmpty) println("Ooops") else println(opt.get) 15 | 16 | if (opt.contains("boo")) println("Opt is non-empty and contains 'boo'") 17 | if (opt.exists(_ > 10)) println("Opt is non-empty and > 10") 18 | if (opt.forall(_ > 10)) println("Opt is empty or > 10") 19 | 20 | if (opt.isDefined) { 21 | val Some(value) = opt 22 | } 23 | 24 | opt match { 25 | case Some(value) => println(value) 26 | case None => println("no value") 27 | } 28 | 29 | opt.getOrElse("No value") 30 | 31 | opt2.orNull 32 | 33 | opt2.foreach(println) 34 | 35 | val opt5 = opt0 orElse opt2 orElse opt3 36 | 37 | val moreThen10: Option[Int] = opt.filter(_ > 10) 38 | val lessOrEqual10: Option[Int] = opt.filterNot(_ > 10) 39 | 40 | val moreThen20: Option[String] = opt.collect { 41 | case i if i > 20 => s"More then 20: $i" 42 | } 43 | 44 | opt.toRight("If opt is empty, I'll be Left[String]") 45 | 46 | opt.toLeft("Nonempty opt will be Left, empty - Right[String]") 47 | 48 | opt.fold("Value for an empty case")((i: Int) => s"The value is $i") 49 | } 50 | 51 | trait FishingOptionExample { 52 | 53 | import Effects._ 54 | trait plain { 55 | val buyBait: String => Bait 56 | val makeBait: String => Bait 57 | val castLine: Bait => Line 58 | val hookFish: Line => Fish 59 | 60 | def goFishing(bestBaitForFish: Option[String]): Option[Fish] = 61 | bestBaitForFish.map(buyBait).map(castLine).map(hookFish) 62 | } 63 | 64 | trait flat { 65 | val buyBait: String => Option[Bait] 66 | val makeBait: String => Option[Bait] 67 | val castLine: Bait => Option[Line] 68 | val hookFish: Line => Option[Fish] 69 | 70 | def goFishingOld(bestBaitForFish: Option[String]): Option[Fish] = 71 | bestBaitForFish.flatMap(buyBait).flatMap(castLine).flatMap(hookFish) 72 | 73 | def goFishing(bestBaitForFish: Option[String]): Option[Fish] = 74 | for { 75 | baitName <- bestBaitForFish 76 | bait <- buyBait(baitName).orElse(makeBait(baitName)) 77 | line <- castLine(bait) 78 | fish <- hookFish(line) 79 | } yield fish 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /Chapter06/src/main/scala/ch06/TryEffect.scala: -------------------------------------------------------------------------------- 1 | package ch06 2 | 3 | import java.io.IOError 4 | 5 | import scala.util._ 6 | import scala.io.StdIn.readLine 7 | 8 | trait TryEffect { 9 | val success = Success("Well") 10 | val failure = Failure(new Exception("Not so well")) 11 | 12 | val firstTry: Try[String] = try { 13 | Success(readLine()) 14 | } catch { 15 | case err: IOError => Failure(err) 16 | } 17 | 18 | val secondTry = Try(readLine()) 19 | 20 | val line = Try { 21 | val line = readLine() 22 | println(s"Got $line from console") 23 | line 24 | } 25 | 26 | if (line.isSuccess) println(s"The line was ${line.get}") 27 | if (line.isFailure) println(s"There was a failure") 28 | 29 | line.filter(_.nonEmpty) 30 | 31 | line.collect { case s: String => s * 10 } 32 | 33 | def retryAfterDelay = ??? 34 | 35 | line.recover { 36 | case ex: NumberFormatException => Math.PI 37 | } 38 | 39 | line.recoverWith { 40 | case ex: NoSuchElementException => Try(retryAfterDelay) 41 | } 42 | 43 | val result = firstTry orElse secondTry orElse failure orElse success 44 | 45 | } 46 | 47 | trait FishingTryExample { 48 | import Effects._ 49 | 50 | trait plain { 51 | val buyBait: String => Bait 52 | val makeBait: String => Bait 53 | val castLine: Bait => Line 54 | val hookFish: Line => Fish 55 | def goFishing(bestBaitForFishOrCurse: Try[String]): Try[Fish] = 56 | bestBaitForFishOrCurse.map(buyBait).map(castLine).map(hookFish) 57 | } 58 | 59 | trait flat { 60 | val buyBait: String => Try[Bait] 61 | val makeBait: String => Try[Bait] 62 | val castLine: Bait => Try[Line] 63 | val hookFish: Line => Try[Fish] 64 | 65 | def goFishing(bestBaitForFishOrCurse: Try[String]): Try[Fish] = for { 66 | baitName <- bestBaitForFishOrCurse 67 | bait <- buyBait(baitName).fold(_ => makeBait(baitName), Success(_)) 68 | line <- castLine(bait) 69 | fish <- hookFish(line) 70 | } yield fish 71 | } 72 | 73 | } 74 | 75 | -------------------------------------------------------------------------------- /Chapter07/build.sbt: -------------------------------------------------------------------------------- 1 | organization := "packt" 2 | version := "1.0-SNAPSHOT" 3 | scalaVersion := "2.13.0" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked") 6 | 7 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" withSources () withJavadoc () 8 | -------------------------------------------------------------------------------- /Chapter07/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 -------------------------------------------------------------------------------- /Chapter07/src/main/scala/ch07/Assessment.scala: -------------------------------------------------------------------------------- 1 | package ch07 2 | 3 | import scala.language.higherKinds 4 | import scala.language.reflectiveCalls 5 | 6 | import scala.util._ 7 | 8 | object Assessment { 9 | implicit val booleanOr: Monoid[Boolean] = new Monoid[Boolean] { 10 | override def identity: Boolean = false 11 | override def op(l: Boolean, r: Boolean): Boolean = l || r 12 | } 13 | 14 | implicit val booleanAnd: Monoid[Boolean] = new Monoid[Boolean] { 15 | override def identity: Boolean = true 16 | override def op(l: Boolean, r: Boolean): Boolean = l && r 17 | } 18 | 19 | implicit def option[A : Monoid]: Monoid[Option[A]] = new Monoid[Option[A]] { 20 | override def identity: Option[A] = None 21 | override def op(l: Option[A], r: Option[A]): Option[A] = (l, r) match { 22 | case (Some(la), Some(lb)) => Option(implicitly[Monoid[A]].op(la, lb)) 23 | case _ => l orElse r 24 | } 25 | } 26 | 27 | def either[L, R : Monoid]: Monoid[Either[L, R]] = new Monoid[Either[L, R]] { 28 | private val ma = implicitly[Monoid[R]] 29 | override def identity: Either[L, R] = Right(ma.identity) 30 | override def op(l: Either[L, R], r: Either[L, R]): Either[L, R] = (l, r) match { 31 | case (l @ Left(_), _) => l 32 | case (_, l @ Left(_)) => l 33 | case (Right(la), Right(lb)) => Right(ma.op(la, lb)) 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Chapter07/src/main/scala/ch07/Foldable.scala: -------------------------------------------------------------------------------- 1 | package ch07 2 | 3 | import scala.language.higherKinds 4 | 5 | trait Foldable[F[_]] { 6 | def foldLeft[A,B](as: F[A])(z: B)(f: (B, A) => B): B 7 | def foldRight[A,B](as: F[A])(z: B)(f: (A, B) => B): B 8 | def foldMap[A,B : Monoid](as: F[A])(f: A => B): B = { 9 | val m = implicitly[Monoid[B]] 10 | foldLeft(as)(m.identity)((b, a) => m.op(f(a), b)) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Chapter07/src/main/scala/ch07/Group.scala: -------------------------------------------------------------------------------- 1 | package ch07 2 | 3 | trait Group[S] extends Monoid[S] { 4 | def inverse(a: S): S 5 | } 6 | 7 | object Group { 8 | 9 | implicit val intAddition: Group[Int] = new Group[Int] { 10 | override def identity: Int = 0 11 | override def op(l: Int, r: Int): Int = l + r 12 | override def inverse(a: Int): Int = identity - a 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Chapter07/src/main/scala/ch07/Monoid.scala: -------------------------------------------------------------------------------- 1 | package ch07 2 | 3 | trait Monoid[S] extends Semigroup[S] { 4 | def identity: S 5 | } 6 | 7 | object Monoid { 8 | 9 | val ZeroFish = Fish(0,0,0,0) 10 | 11 | type Bucket[S] = List[S] 12 | 13 | implicit val mergeBuckets: Monoid[Bucket[Fish]] = new Monoid[Bucket[Fish]] { 14 | override def identity: Bucket[Fish] = List.empty[Fish] 15 | override def op(l: Bucket[Fish], r: Bucket[Fish]): Bucket[Fish] = l ++ r 16 | } 17 | 18 | implicit val intAddition: Monoid[Int] = new Monoid[Int] { 19 | override def identity: Int = 0 20 | override def op(l: Int, r: Int): Int = l + r 21 | } 22 | 23 | implicit val intMultiplication: Monoid[Int] = new Monoid[Int] { 24 | override def identity: Int = 1 25 | override def op(l: Int, r: Int): Int = l * r 26 | } 27 | 28 | implicit val stringConcatenation: Monoid[String] = new Monoid[String] { 29 | override def identity: String = "" 30 | override def op(l: String, r: String): String = l + r 31 | } 32 | 33 | implicit val volumeMonoid: Monoid[Fish] = new Monoid[Fish] { 34 | override def identity: Fish = ZeroFish 35 | override def op(l: Fish, r: Fish): Fish = 36 | if (l.volume > r.volume) l.eat(r) else r.eat(l) 37 | } 38 | 39 | implicit val weightMonoid: Monoid[Fish] = new Monoid[Fish] { 40 | override def identity: Fish = ZeroFish 41 | override def op(l: Fish, r: Fish): Fish = 42 | if (l.weight > r.weight) l.eat(r) else r.eat(l) 43 | } 44 | 45 | implicit val poisonMonoid: Monoid[Fish] = new Monoid[Fish] { 46 | override def identity: Fish = ZeroFish 47 | override def op(l: Fish, r: Fish): Fish = 48 | if (l.poisonousness > r.poisonousness) l else r 49 | } 50 | 51 | implicit val teethMonoid: Monoid[Fish] = new Monoid[Fish] { 52 | override def identity: Fish = ZeroFish 53 | override def op(l: Fish, r: Fish): Fish = 54 | if (l.teeth > r.teeth) l else r 55 | } 56 | 57 | implicit def surviveInTheBucket(implicit m: Monoid[Fish]): Monoid[Bucket[Fish]] = new Monoid[Bucket[Fish]] { 58 | override def identity: Bucket[Fish] = List.fill(100)(ZeroFish) 59 | 60 | override def op(l: Bucket[Fish], r: Bucket[Fish]): Bucket[Fish] = { 61 | val operation = (m.op _).tupled 62 | l zip r map operation 63 | } 64 | } 65 | 66 | implicit val slowPoisonMonoid: Monoid[Fish] = new Monoid[Fish] { 67 | override def identity: Fish = ZeroFish 68 | override def op(l: Fish, r: Fish): Fish = { 69 | Thread.sleep(1) 70 | if (l.poisonousness > r.poisonousness) l else r 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Chapter07/src/main/scala/ch07/MonoidFoldable.scala: -------------------------------------------------------------------------------- 1 | package ch07 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | import scala.language.higherKinds 5 | 6 | trait MonoidFoldable[A, F[_]] { 7 | 8 | def foldRight(as: F[A]): A 9 | def foldLeft(as: F[A]): A 10 | def foldBalanced(as: F[A]): A 11 | def foldPar(as: F[A])(implicit ec: ExecutionContext): Future[A] 12 | } 13 | 14 | object MonoidFoldable { 15 | 16 | implicit def listMonoidFoldable[A : Monoid]: MonoidFoldable[A, List] = new MonoidFoldable[A, List] { 17 | private val m = implicitly[Monoid[A]] 18 | override def foldRight(as: List[A]): A = as.foldRight(m.identity)(m.op) 19 | 20 | override def foldLeft(as: List[A]): A = as.foldLeft(m.identity)(m.op) 21 | 22 | override def foldBalanced(as: List[A]): A = as match { 23 | case Nil => m.identity 24 | case List(one) => one 25 | case _ => val (l, r) = as.splitAt(as.length/2) 26 | m.op(foldBalanced(l), foldBalanced(r)) 27 | } 28 | 29 | private val parallelLimit = 10 30 | override def foldPar(as: List[A])(implicit ec: ExecutionContext): Future[A] = { 31 | if (as.length < parallelLimit) Future(foldBalanced(as)) 32 | else { 33 | val (l, r) = as.splitAt(as.length/2) 34 | Future.reduceLeft(List(foldPar(l), foldPar(r)))(m.op) 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Chapter07/src/main/scala/ch07/Reducible.scala: -------------------------------------------------------------------------------- 1 | package ch07 2 | 3 | trait Reducible[A] { 4 | 5 | @throws("UnsupportedOperationException") 6 | def reduceLeft[B >: A](op: (B, A) => B): B 7 | 8 | @throws("UnsupportedOperationException") 9 | def reduceRight[B >: A](op: (A, B) => B): B 10 | 11 | @throws("UnsupportedOperationException") 12 | def reduce[B >: A](op: (B, B) => B): B = reduceLeft(op) 13 | 14 | def reduceLeftOption[B >: A](op: (B, A) => B): Option[B] 15 | 16 | def reduceRightOption[B >: A](op: (A, B) => B): Option[B] 17 | 18 | def reduceOption[B >: A](op: (B, B) => B): Option[B] = reduceLeftOption(op) 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Chapter07/src/main/scala/ch07/Semigroup.scala: -------------------------------------------------------------------------------- 1 | package ch07 2 | 3 | final case class Fish(volume: Int, weight: Int, teeth: Int, poisonousness: Int) { 4 | def eat(f: Fish): Fish = Fish(volume + f.volume, weight + f.weight, teeth + f.teeth, poisonousness + f.poisonousness) 5 | } 6 | 7 | trait Semigroup[S] { 8 | def op(l: S, r: S): S 9 | } 10 | 11 | object Semigroup { 12 | 13 | implicit val volumeSemigroup: Semigroup[Fish] = (l: Fish, r: Fish) => 14 | if (l.volume > r.volume) l.eat(r) else r.eat(l) 15 | 16 | implicit val weightSemigroup: Semigroup[Fish] = (l: Fish, r: Fish) => 17 | if (l.weight > r.weight) l.eat(r) else r.eat(l) 18 | 19 | implicit val poisonSemigroup: Semigroup[Fish] = (l: Fish, r: Fish) => 20 | if (l.poisonousness > r.poisonousness) l else r 21 | 22 | implicit val teethSemigroup: Semigroup[Fish] = (l: Fish, r: Fish) => 23 | if (l.teeth > r.teeth) l else r 24 | 25 | implicit val intAddition: Semigroup[Int] = (l: Int, r: Int) => l + r 26 | 27 | implicit val intMultiplication: Semigroup[Int] = (l: Int, r: Int) => l * r 28 | 29 | implicit val stringConcatenation: Semigroup[String] = (l: String, r: String) => l + r 30 | } -------------------------------------------------------------------------------- /Chapter07/src/test/scala/ch07/AssessmentSpecification.scala: -------------------------------------------------------------------------------- 1 | package ch07 2 | 3 | import org.scalacheck._ 4 | 5 | object AssessmentSpecification extends Properties("Assessment") { 6 | 7 | import MonoidSpecification._ 8 | 9 | property("boolean under or") = { 10 | import Assessment.booleanOr 11 | monoidProp[Boolean] 12 | } 13 | 14 | property("boolean under and") = { 15 | import Assessment.booleanAnd 16 | monoidProp[Boolean] 17 | } 18 | 19 | property("Option[Int] under addition") = { 20 | import Monoid.intAddition 21 | import Assessment.option 22 | monoidProp[Option[Int]] 23 | } 24 | 25 | property("Option[String] under concatenation") = { 26 | import Monoid.stringConcatenation 27 | import Assessment.option 28 | monoidProp[Option[String]] 29 | } 30 | 31 | property("Either[Int] under multiplication") = { 32 | import Monoid.intMultiplication 33 | implicit val monoid: Monoid[Either[Unit, Int]] = Assessment.either[Unit, Int] 34 | monoidProp[Either[Unit, Int]] 35 | } 36 | 37 | property("Either[Boolean] under OR") = { 38 | import Assessment.booleanOr 39 | implicit val monoid: Monoid[Either[String, Boolean]] = Assessment.either[String, Boolean] 40 | monoidProp[Either[String, Boolean]] 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Chapter07/src/test/scala/ch07/GroupSpecification.scala: -------------------------------------------------------------------------------- 1 | package ch07 2 | 3 | import org.scalacheck.Prop._ 4 | import org.scalacheck._ 5 | 6 | object GroupSpecification extends Properties("Group") { 7 | 8 | import MonoidSpecification._ 9 | 10 | def invertibility[S : Group : Arbitrary]: Prop = 11 | forAll((a: S) => { 12 | val m = implicitly[Group[S]] 13 | m.op(a, m.inverse(a)) == m.identity && m.op(m.inverse(a), a) == m.identity 14 | }) 15 | 16 | def groupProp[S : Group: Arbitrary]: Prop = monoidProp[S] && invertibility[S] 17 | 18 | def commutativity[S : Group : Arbitrary]: Prop = 19 | forAll((a: S, b: S) => { 20 | val m = implicitly[Group[S]] 21 | m.op(a, b) == m.op(b, a) 22 | }) 23 | 24 | def abelianGroupProp[S : Group: Arbitrary]: Prop = groupProp[S] && commutativity[S] 25 | 26 | property("ints under addition form a group") = { 27 | import Group.intAddition 28 | groupProp[Int] 29 | } 30 | 31 | property("ints under addition form an abelian group") = { 32 | import Group.intAddition 33 | groupProp[Int] 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Chapter07/src/test/scala/ch07/MonoidFoldableSpecification.scala: -------------------------------------------------------------------------------- 1 | package ch07 2 | 3 | import ch07.Monoid.Bucket 4 | import ch07.SemigroupSpecification.fishGen 5 | import org.scalacheck.Prop._ 6 | import org.scalacheck.Test.Parameters 7 | import org.scalacheck._ 8 | 9 | import scala.concurrent.Await 10 | 11 | object MonoidFoldableSpecification extends Properties("MonoidFoldable") { 12 | implicit val params: Parameters = Parameters.default.withMinSuccessfulTests(10) 13 | 14 | import scala.concurrent.ExecutionContext.Implicits.global 15 | import scala.concurrent.duration._ 16 | 17 | val bucketOfFishGen: Gen[List[Fish]] = for { 18 | n <- Gen.choose(100, 1000) 19 | gen <- Gen.listOfN(n, fishGen) 20 | } yield gen 21 | 22 | implicit val arbBucketOfFish: Arbitrary[Bucket[Fish]] = Arbitrary(bucketOfFishGen) 23 | 24 | def withTime(block: => Fish): (Fish, Long) = { 25 | val start = System.nanoTime() 26 | val result = block 27 | (result, (System.nanoTime() - start) / 1000000) 28 | } 29 | 30 | property("foldPar is the quickest way to fold a list") = { 31 | import Monoid.slowPoisonMonoid 32 | val foldable = MonoidFoldable.listMonoidFoldable[Fish] 33 | 34 | forAllNoShrink((as: Bucket[Fish]) => { 35 | val (left, leftRuntime) = withTime(foldable.foldLeft(as)) 36 | val (right, rightRuntime) = withTime(foldable.foldRight(as)) 37 | val (balanced, balancedRuntime) = withTime(foldable.foldBalanced(as)) 38 | val (parallel, parallelRuntime) = withTime(Await.result(foldable.foldPar(as), 5.seconds)) 39 | 40 | s"${as.size} fishes: $leftRuntime, $rightRuntime, $balancedRuntime, $parallelRuntime millis" |: all( 41 | "all results are equal" |: all(left == right, left == balanced, left == parallel), 42 | "parallel is quickest" |: parallelRuntime <= List(leftRuntime, rightRuntime, balancedRuntime).min 43 | ) 44 | }) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Chapter07/src/test/scala/ch07/MonoidSpecification.scala: -------------------------------------------------------------------------------- 1 | package ch07 2 | 3 | import ch07.Monoid.Bucket 4 | import ch07.SemigroupSpecification.{associativity, fishGen} 5 | import org.scalacheck.Prop._ 6 | import org.scalacheck._ 7 | 8 | object MonoidSpecification extends Properties("Monoid") { 9 | 10 | def identity[S : Monoid : Arbitrary]: Prop = 11 | forAll((a: S) => { 12 | val m = implicitly[Monoid[S]] 13 | m.op(a, m.identity) == a && m.op(m.identity, a) == a 14 | }) 15 | 16 | 17 | def monoidProp[S : Monoid : Arbitrary]: Prop = associativity[S] && identity[S] 18 | 19 | val bucketOfFishGen: Gen[List[Fish]] = Gen.listOf(fishGen) 20 | implicit val arbBucketOfFish: Arbitrary[Bucket[Fish]] = Arbitrary(bucketOfFishGen) 21 | 22 | import SemigroupSpecification._ 23 | 24 | property("bucket of fish monoid") = { 25 | import Monoid.mergeBuckets 26 | monoidProp[Bucket[Fish]] 27 | } 28 | 29 | property("ints under addition") = { 30 | import Monoid.intAddition 31 | monoidProp[Int] 32 | } 33 | 34 | property("ints under multiplication") = { 35 | import Monoid.intMultiplication 36 | monoidProp[Int] 37 | } 38 | 39 | property("strings under concatenation") = { 40 | import Monoid.stringConcatenation 41 | monoidProp[String] 42 | } 43 | 44 | property("props for fish monoid under 'big eats little'") = { 45 | import Monoid.volumeMonoid 46 | monoidProp[Fish] 47 | } 48 | 49 | property("props for fish monoid under 'heavy eats light'") = { 50 | import Monoid.weightMonoid 51 | monoidProp[Fish] 52 | } 53 | 54 | property("props for fish monoid under 'poisonous drives away others'") = { 55 | import Monoid.poisonMonoid 56 | monoidProp[Fish] 57 | } 58 | property("props for fish monoid under 'teeth FTW'") = { 59 | import Monoid.teethMonoid 60 | monoidProp[Fish] 61 | } 62 | 63 | property("props for survival in the bucket for most poisonousness") = { 64 | import Monoid.poisonMonoid 65 | import Monoid.surviveInTheBucket 66 | monoidProp[Bucket[Fish]] 67 | } 68 | 69 | property("props for survival in the bucket for heaviest") = { 70 | import Monoid.weightMonoid 71 | import Monoid.surviveInTheBucket 72 | monoidProp[Bucket[Fish]] 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /Chapter07/src/test/scala/ch07/SemigroupSpecification.scala: -------------------------------------------------------------------------------- 1 | package ch07 2 | 3 | import org.scalacheck._ 4 | import org.scalacheck.Prop._ 5 | 6 | object SemigroupSpecification extends Properties("Semigroup") { 7 | 8 | def associativity[S : Semigroup : Arbitrary]: Prop = 9 | forAll((a: S, b: S, c: S) => { 10 | val sg = implicitly[Semigroup[S]] 11 | sg.op(sg.op(a, b), c) == sg.op(a, sg.op(b, c)) 12 | }) 13 | 14 | val fishGen: Gen[Fish] = for { 15 | weight <- Gen.posNum[Int] 16 | volume <- Gen.posNum[Int] 17 | poisonousness <- Gen.posNum[Int] 18 | teeth <- Gen.posNum[Int] 19 | } yield Fish(volume, weight, teeth, poisonousness) 20 | 21 | implicit val arbFish: Arbitrary[Fish] = Arbitrary(fishGen) 22 | 23 | property("associativity for fish semigroup under 'big eats little'") = { 24 | import Semigroup.volumeSemigroup 25 | associativity[Fish] 26 | } 27 | 28 | property("associativity for fish semigroup under 'heavy eats light'") = { 29 | import Semigroup.weightSemigroup 30 | associativity[Fish] 31 | } 32 | 33 | property("associativity for fish semigroup under 'poisonous drives away others'") = { 34 | import Semigroup.poisonSemigroup 35 | associativity[Fish] 36 | } 37 | property("associativity for fish semigroup under 'teeth FTW'") = { 38 | import Semigroup.teethSemigroup 39 | associativity[Fish] 40 | } 41 | 42 | property("associativity for int under addition") = { 43 | import Semigroup.intAddition 44 | associativity[Int] 45 | } 46 | property("associativity for int under multiplication") = { 47 | import Semigroup.intMultiplication 48 | associativity[Int] 49 | } 50 | property("associativity for strings under concatenation") = { 51 | import Semigroup.stringConcatenation 52 | associativity[String] 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /Chapter08/build.sbt: -------------------------------------------------------------------------------- 1 | organization := "packt" 2 | version := "1.0-SNAPSHOT" 3 | scalaVersion := "2.13.0" 4 | 5 | lazy val ch07 = RootProject(file("../Chapter07")) 6 | 7 | lazy val ch08 = project 8 | .in(file(".")) 9 | .settings( 10 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked"), 11 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" withSources () withJavadoc () 12 | ) 13 | .dependsOn(ch07) 14 | -------------------------------------------------------------------------------- /Chapter08/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /Chapter08/src/main/scala/ch08/Applicative.scala: -------------------------------------------------------------------------------- 1 | package ch08 2 | 3 | import scala.language.{higherKinds, reflectiveCalls} 4 | import scala.util.{Failure, Success, Try} 5 | 6 | trait Applicative[F[_]] extends Functor[F] { 7 | def apply[A,B](a: F[A])(f: F[A => B]): F[B] 8 | def unit[A](a: => A): F[A] 9 | 10 | override def map[A,B](fa: F[A])(f: A => B): F[B] = 11 | apply(fa)(unit(f)) 12 | 13 | def map2[A,B,C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = 14 | apply(fb)(map(fa)(f.curried)) 15 | 16 | def map3[A,B,C,D](fa: F[A], 17 | fb: F[B], 18 | fc: F[C])(f: (A, B, C) => D): F[D] = 19 | apply(fc)(apply(fb)(apply(fa)(unit(f.curried)))) 20 | 21 | def map4[A,B,C,D,E](fa: F[A], 22 | fb: F[B], 23 | fc: F[C], 24 | fd: F[D])(f: (A, B, C, D) => E): F[E] = { 25 | val ff: (A, B, C) => D => E = (a,b,c) => d => f(a,b,c,d) 26 | apply(fd)(map3(fa, fb, fc)(ff)) 27 | } 28 | 29 | def product[G[_]](G: Applicative[G]): Applicative[({type f[x] = (F[x], G[x])})#f] = { 30 | val F = this 31 | new Applicative[({type f[x] = (F[x], G[x])})#f] { 32 | def unit[A](a: => A) = (F.unit(a), G.unit(a)) 33 | override def apply[A,B](p: (F[A], G[A]))(fs: (F[A => B], G[A => B])) = 34 | (F.apply(p._1)(fs._1), G.apply(p._2)(fs._2)) 35 | } 36 | } 37 | 38 | def compose[G[_]](G: Applicative[G]): Applicative[({type f[x] = F[G[x]]})#f] = { 39 | val F = this 40 | 41 | def fab[A, B]: G[A => B] => G[A] => G[B] = (gf: G[A => B]) => (ga: G[A]) => G.apply(ga)(gf) 42 | 43 | def fg[B, A](f: F[G[A => B]]): F[G[A] => G[B]] = F.map(f)(fab) 44 | 45 | new Applicative[({type f[x] = F[G[x]]})#f] { 46 | def unit[A](a: => A) = F.unit(G.unit(a)) 47 | override def apply[A, B](a: F[G[A]])(f: F[G[A => B]]): F[G[B]] = 48 | F.apply(a)(fg(f)) 49 | } 50 | } 51 | } 52 | 53 | 54 | 55 | object Applicative { 56 | implicit val bucketApplicative: Applicative[List] = new Applicative[List] { 57 | 58 | override def apply[A, B](a: List[A])(f: List[A => B]): List[B] = (a, f) match { 59 | case (Nil, _) => Nil 60 | case (_, Nil) => Nil 61 | case (aa :: as, ff :: fs) => 62 | val fab: (A => B) => B = f => f(aa) 63 | ff(aa) :: as.map(ff) ::: fs.map(fab) ::: apply(as)(fs) 64 | case other => Nil 65 | } 66 | 67 | override def unit[A](a: => A): List[A] = List(a) 68 | } 69 | 70 | implicit val optionApplicative: Applicative[Option] = new Applicative[Option] { 71 | override def apply[A, B](a: Option[A])(f: Option[A => B]): Option[B] = (a,f) match { 72 | case (Some(a), Some(f)) => Some(f(a)) 73 | case _ => None 74 | } 75 | override def unit[A](a: => A): Option[A] = Some(a) 76 | } 77 | 78 | implicit def eitherApplicative[L] = new Applicative[({ type T[A] = Either[L, A] })#T] { 79 | override def apply[A, B](a: Either[L, A])(f: Either[L, A => B]): Either[L, B] = (a, f) match { 80 | case (Right(a), Right(f)) => Right(f(a)) 81 | case (Left(l), _) => Left(l) 82 | case (_, Left(l)) => Left(l) 83 | } 84 | override def unit[A](a: => A): Either[L, A] = Right(a) 85 | } 86 | 87 | implicit val tryApplicative: Applicative[Try] = new Applicative[Try] { 88 | override def apply[A, B](a: Try[A])(f: Try[A => B]): Try[B] = (a, f) match { 89 | case (Success(a), Success(f)) => Try(f(a)) 90 | case (Failure(ex), _) => Failure(ex) 91 | case (_, Failure(ex)) => Failure(ex) 92 | } 93 | override def unit[A](a: => A): Try[A] = Success(a) 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /Chapter08/src/main/scala/ch08/Functor.scala: -------------------------------------------------------------------------------- 1 | package ch08 2 | 3 | import scala.language.{higherKinds, reflectiveCalls} 4 | import scala.util.Try 5 | 6 | trait Functor[F[_]] { 7 | def map[A,B](in: F[A])(f: A => B): F[B] 8 | 9 | def mapC[A,B](f: A => B): F[A] => F[B] = fa => map(fa)(f) 10 | } 11 | 12 | object Functor { 13 | implicit val bucketFunctor: Functor[List] = new Functor[List] { 14 | override def map[A, B](in: List[A])(f: A => B): List[B] = in.map(f) 15 | 16 | override def mapC[A, B](f: A => B): List[A] => List[B] = (_: List[A]).map(f) 17 | } 18 | 19 | implicit val optionFunctor: Functor[Option] = new Functor[Option] { 20 | override def map[A, B](in: Option[A])(f: A => B): Option[B] = in.map(f) 21 | override def mapC[A, B](f: A => B): Option[A] => Option[B] = (_: Option[A]).map(f) 22 | } 23 | 24 | implicit def eitherFunctor[L] = new Functor[({ type T[A] = Either[L, A] })#T] { 25 | override def map[A, B](in: Either[L, A])(f: A => B): Either[L, B] = in.map(f) 26 | override def mapC[A, B](f: A => B): Either[L, A] => Either[L, B] = (_: Either[L, A]).map(f) 27 | } 28 | 29 | implicit val tryFunctor: Functor[Try] = new Functor[Try] { 30 | override def map[A, B](in: Try[A])(f: A => B): Try[B] = in.map(f) 31 | override def mapC[A, B](f: A => B): Try[A] => Try[B] = (_: Try[A]).map(f) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Chapter08/src/main/scala/ch08/Traversable.scala: -------------------------------------------------------------------------------- 1 | package ch08 2 | 3 | import ch08.Model.Bucket 4 | 5 | import scala.language.{higherKinds, reflectiveCalls} 6 | import scala.util.{Failure, Success, Try} 7 | import scala.{Traversable => _} 8 | 9 | trait Traversable[F[_]] extends Functor[F] { 10 | def traverse[A,B,G[_]: Applicative](a: F[A])(f: A => G[B]): G[F[B]] 11 | def sequence[A,G[_]: Applicative](a: F[G[A]]): G[F[A]] = traverse(a)(identity) 12 | 13 | implicit def compose[H[_]](implicit H: Traversable[H]): Traversable[({type f[x] = F[H[x]]})#f] = { 14 | val F = this 15 | new Traversable[({type f[x] = F[H[x]]})#f] { 16 | override def traverse[A, B, G[_] : Applicative](fa: F[H[A]])(f: A => G[B]) = 17 | F.traverse(fa)((ga: H[A]) => H.traverse(ga)(f)) 18 | 19 | override def map[A, B](in: F[H[A]])(f: A => B): F[H[B]] = 20 | F.map(in)((ga: H[A]) => H.map(ga)(f)) 21 | } 22 | } 23 | } 24 | 25 | object Traversable { 26 | 27 | implicit val bucketTraversable = new Traversable[Bucket] { 28 | override def map[A, B](in: Bucket[A])(f: A => B): Bucket[B] = Functor.bucketFunctor.map(in)(f) 29 | override def traverse[A, B, G[_] : Applicative](a: Bucket[A])(f: A => G[B]): G[Bucket[B]] = { 30 | val G = implicitly[Applicative[G]] 31 | a.foldRight(G.unit(List[B]()))((aa, fbs) => G.map2(f(aa), fbs)(_ :: _)) 32 | } 33 | } 34 | 35 | implicit val optionTraversable = new Traversable[Option] { 36 | override def map[A, B](in: Option[A])(f: A => B): Option[B] = Functor.optionFunctor.map(in)(f) 37 | override def traverse[A, B, G[_] : Applicative](a: Option[A])(f: A => G[B]): G[Option[B]] = { 38 | val G = implicitly[Applicative[G]] 39 | a match { 40 | case Some(s) => G.map(f(s))(Some.apply) 41 | case None => G.unit(None) 42 | } 43 | } 44 | } 45 | 46 | implicit val tryTraversable = new Traversable[Try] { 47 | override def map[A, B](in: Try[A])(f: A => B): Try[B] = Functor.tryFunctor.map(in)(f) 48 | override def traverse[A, B, G[_] : Applicative](a: Try[A])(f: A => G[B]): G[Try[B]] = { 49 | val G = implicitly[Applicative[G]] 50 | a match { 51 | case Success(s) => G.map(f(s))(Success.apply) 52 | case Failure(ex) => G.unit(Failure(ex)) // re-wrap the ex to change the type of Failure 53 | } 54 | } 55 | } 56 | 57 | implicit def eitherTraversable[L] = new Traversable[({ type T[A] = Either[L, A] })#T] { 58 | override def map[A, B](in: Either[L, A])(f: A => B): Either[L, B] = Functor.eitherFunctor[L].map(in)(f) 59 | override def traverse[A, B, G[_] : Applicative](a: Either[L, A])(f: A => G[B]): G[Either[L, B]] = { 60 | val G = implicitly[Applicative[G]] 61 | a match { 62 | case Right(s) => G.map(f(s))(Right.apply) 63 | case Left(l) => G.unit(Left(l)) // re-wrap the l to change the type of Failure 64 | } 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /Chapter08/src/test/scala/ch08/ApplicativeSpecification.scala: -------------------------------------------------------------------------------- 1 | package ch08 2 | 3 | import org.scalacheck.Prop._ 4 | import org.scalacheck._ 5 | 6 | import scala.language.higherKinds 7 | import scala.util.Try 8 | 9 | object ApplicativeSpecification extends Properties("Applicative") { 10 | 11 | def identityProp[A, F[_]](implicit A: Applicative[F], 12 | arbFA: Arbitrary[F[A]]): Prop = 13 | forAll { as: F[A] => 14 | A(as)(A.unit((a: A) => a)) == as 15 | } 16 | 17 | def homomorphism[A, B, F[_]](implicit A: Applicative[F], 18 | arbA: Arbitrary[A], 19 | arbB: Arbitrary[B], 20 | cogenA: Cogen[A]): Prop = { 21 | forAll((f: A => B, a: A) => { 22 | A(A.unit(a))(A.unit(f)) == A.unit(f(a)) 23 | }) 24 | } 25 | 26 | def interchange[A, B, F[_]](implicit A: Applicative[F], 27 | arbFA: Arbitrary[F[A]], 28 | arbA: Arbitrary[A], 29 | arbB: Arbitrary[B], 30 | cogenA: Cogen[A]): Prop = { 31 | forAll((f: A => B, a: A) => { 32 | val leftSide = A(A.unit(a))(A.unit(f)) 33 | val func = (ff: A => B) => ff(a) 34 | val rightSide = A(A.unit(f))(A.unit(func)) 35 | leftSide == rightSide 36 | }) 37 | } 38 | 39 | def composeF[A, B, C]: (B => C) => (A => B) => (A => C) = _.compose 40 | 41 | def composition[A, B, C, F[_]](implicit A: Applicative[F], 42 | arbFA: Arbitrary[F[A]], 43 | arbB: Arbitrary[B], 44 | arbC: Arbitrary[C], 45 | cogenA: Cogen[A], 46 | cogenB: Cogen[B]): Prop = { 47 | forAll((as: F[A], f: A => B, g: B => C) => { 48 | val af: F[A => B] = A.unit(f) 49 | val ag: F[B => C] = A.unit(g) 50 | val ac: F[(B => C) => (A => B) => (A => C)] = A.unit(composeF) 51 | val leftSide = A(as)(A(af)(A(ag)(ac))) 52 | val rightSide = A(A(as)(af))(ag) 53 | 54 | leftSide == rightSide 55 | }) 56 | } 57 | 58 | def applicative[A, B, C, F[_]](implicit fu: Applicative[F], 59 | arbFA: Arbitrary[F[A]], 60 | arbA: Arbitrary[A], 61 | arbB: Arbitrary[B], 62 | arbC: Arbitrary[C], 63 | cogenA: Cogen[A], 64 | cogenB: Cogen[B]): Prop = { 65 | identityProp[A, F] && homomorphism[A, B, F] && 66 | interchange[A, B, F] && composition[A, B, C, F] && 67 | ch08.FunctorSpecification.functor[A, B, C, F] 68 | } 69 | 70 | import Applicative._ 71 | 72 | property("Applicative[Option] and Int => String, String => Long") = { 73 | applicative[Int, String, Long, Option] 74 | } 75 | 76 | property("Applicative[Option] and String => Int, Int => Boolean") = { 77 | applicative[String, Int, Boolean, Option] 78 | } 79 | 80 | property("Applicative[Try] and Int => String, String => Long") = { 81 | applicative[Int, String, Long, Try] 82 | } 83 | 84 | property("Applicative[Try] and String => Int, Int => Boolean") = { 85 | applicative[String, Int, Boolean, Try] 86 | } 87 | 88 | type UnitEither[R] = Either[Unit, R] 89 | 90 | property("Applicative[Either] and Int => String, String => Long") = { 91 | applicative[Int, String, Long, UnitEither] 92 | } 93 | 94 | property("Applicative[Either] and String => Int, Int => Boolean") = { 95 | applicative[String, Int, Boolean, UnitEither] 96 | } 97 | 98 | property("Applicative[List] and Int => String, String => Long") = { 99 | applicative[Int, String, Long, List] 100 | } 101 | 102 | property("Applicative[List] and String => Int, Int => Boolean") = { 103 | applicative[String, Int, Boolean, List] 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Chapter08/src/test/scala/ch08/FunctorSpecification.scala: -------------------------------------------------------------------------------- 1 | package ch08 2 | 3 | import org.scalacheck._ 4 | import org.scalacheck.Prop._ 5 | 6 | import scala.language.higherKinds 7 | import scala.util.Try 8 | 9 | object FunctorSpecification extends Properties("Functor") { 10 | 11 | def id[A, F[_]](implicit F: Functor[F], arbFA: Arbitrary[F[A]]): Prop = 12 | forAll { as: F[A] => F.map(as)(identity) == as } 13 | 14 | def associativity[A, B, C, F[_]](implicit F: Functor[F], 15 | arbFA: Arbitrary[F[A]], 16 | arbB: Arbitrary[B], 17 | arbC: Arbitrary[C], 18 | cogenA: Cogen[A], 19 | cogenB: Cogen[B]): Prop = { 20 | forAll((as: F[A], f: A => B, g: B => C) => { 21 | F.map(F.map(as)(f))(g) == F.map(as)(f andThen g) 22 | }) 23 | } 24 | 25 | def functor[A, B, C, F[_]](implicit F: Functor[F], arbFA: Arbitrary[F[A]], 26 | arbB: Arbitrary[B], 27 | arbC: Arbitrary[C], 28 | cogenA: Cogen[A], 29 | cogenB: Cogen[B]): Prop = 30 | id[A, F] && associativity[A, B, C, F] 31 | 32 | import Functor._ 33 | 34 | property("Functor[Option] and Int => String, String => Long") = { 35 | functor[Int, String, Long, Option] 36 | } 37 | 38 | property("Functor[Option] and String => Int, Int => Boolean") = { 39 | functor[String, Int, Boolean, Option] 40 | } 41 | 42 | type UnitEither[R] = Either[Unit, R] 43 | 44 | property("Functor[Either] and Int => String, String => Long") = { 45 | functor[Int, String, Long, UnitEither] 46 | } 47 | 48 | property("Functor[Either] and String => Int, Int => Boolean") = { 49 | functor[String, Int, Boolean, UnitEither] 50 | } 51 | 52 | property("Functor[Try] and Int => String, String => Long") = { 53 | functor[Int, String, Long, Try] 54 | } 55 | 56 | property("Functor[Try] and String => Int, Int => Boolean") = { 57 | functor[String, Int, Boolean, Try] 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Chapter09/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / organization := "packt" 2 | ThisBuild / version := "1.0-SNAPSHOT" 3 | ThisBuild / scalaVersion := "2.13.0" 4 | 5 | lazy val ch07 = RootProject(file("../Chapter07")) 6 | 7 | lazy val ch08 = RootProject(file("../Chapter08")) 8 | 9 | lazy val ch09 = project 10 | .in(file(".")) 11 | .settings( 12 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked"), 13 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" withSources () withJavadoc (), 14 | addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.10.3") 15 | ) 16 | .dependsOn(ch07, ch08) 17 | -------------------------------------------------------------------------------- /Chapter09/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /Chapter09/src/main/scala/ch09/Assesments.scala: -------------------------------------------------------------------------------- 1 | package ch09 2 | 3 | import scala.language.{higherKinds, implicitConversions} 4 | import scala.util.{Failure, Random, Success, Try} 5 | import Boat.{boat, _} 6 | import ch09.WriterExample.WriterTracking 7 | 8 | object OptionExample extends App { 9 | 10 | import Monad.optionMonad 11 | 12 | def go(speed: Float, time: Float)(boat: Boat): Option[Boat] = 13 | if (Random.nextInt(2) == 0) None 14 | else Option(boat.go(speed, time)) 15 | 16 | println(move(go, turn[Option])(Option(boat))) 17 | 18 | } 19 | 20 | object TryExample extends App { 21 | 22 | import Monad.tryMonad 23 | 24 | def go(speed: Float, time: Float)(boat: Boat): Try[Boat] = 25 | if (Random.nextInt(100) == 0) Failure(new Exception("Motor malfunction")) 26 | else Success(boat.go(speed, time)) 27 | 28 | println(move(go, turn[Try])(Success(boat))) 29 | 30 | } 31 | 32 | object EitherExample extends App { 33 | 34 | import Monad.eitherMonad 35 | type ErrorOr[B] = Either[String, B] 36 | 37 | def go(speed: Float, time: Float)(boat: Boat): ErrorOr[Boat] = 38 | if (Random.nextInt(100) == 0) Left("Motor malfunction") 39 | else Right(boat.go(speed, time)) 40 | 41 | println(move(go, turn[ErrorOr])(Right(boat))) 42 | 43 | } 44 | 45 | object WriterOptionExample extends App { 46 | type WriterOption[B] = Writer[Vector[(Double, Double)], Option[B]] 47 | import WriterExample.vectorMonoid 48 | 49 | def go(speed: Float, time: Float)(boat: Boat): WriterOption[Boat] = { 50 | val b: Option[Boat] = OptionExample.go(speed, time)(boat) 51 | val c: WriterTracking[Boat] = WriterExample.go(speed, time)(boat) 52 | Writer((b, c.run._2)) 53 | } 54 | 55 | private def writerOption[A](a: A) = 56 | Writer[Vector[(Double, Double)], Option[A]](Option(a)) 57 | 58 | implicit val readerWriterMonad = new Monad[WriterOption] { 59 | override def flatMap[A, B](wr: WriterOption[A])(f: A => WriterOption[B]): WriterOption[B] = 60 | wr.compose { 61 | case Some(a) => f(a) 62 | case None => Writer(Option.empty[B]) 63 | } 64 | 65 | override def unit[A](a: => A): WriterOption[A] = writerOption(a) 66 | } 67 | 68 | println(move(go, turn)(writerOption(boat)).run) 69 | } 70 | -------------------------------------------------------------------------------- /Chapter09/src/main/scala/ch09/Boat.scala: -------------------------------------------------------------------------------- 1 | package ch09 2 | 3 | final case class Boat(direction: Double, position: (Double, Double)) { 4 | def go(speed: Float, time: Float): Boat = { 5 | val distance = speed * time 6 | val (x, y) = position 7 | val nx = x + distance * Math.cos(direction) 8 | val ny = y + distance * Math.sin(direction) 9 | copy(direction, (nx, ny)) 10 | } 11 | def turn(angle: Double): Boat = 12 | copy(direction = (this.direction + angle) % (2 * Math.PI)) 13 | } 14 | 15 | import scala.language.{higherKinds, implicitConversions} 16 | 17 | object Boat { 18 | val boat = Boat(0, (0d, 0d)) 19 | 20 | import Monad.lowPriorityImplicits._ 21 | 22 | def go[M[_]: Monad]: (Float, Float) => Boat => M[Boat] = 23 | (speed, time) => boat => Monad[M].unit(boat.go(speed, time)) 24 | 25 | def turn[M[_]: Monad]: Double => Boat => M[Boat] = 26 | angle => boat => Monad[M].unit(boat.turn(angle)) 27 | 28 | def move[A, M[_]: Monad](go: (Float, Float) => A => M[A], turn: Double => A => M[A])(boat: M[A]): M[A] = for { 29 | a <- boat 30 | b <- go(10,5)(a) 31 | c <- turn(0.5)(b) 32 | d <- go(20, 20)(c) 33 | e <- turn(-0.1)(d) 34 | f <- go(1,1)(e) 35 | } yield f 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Chapter09/src/main/scala/ch09/Ch09.scala: -------------------------------------------------------------------------------- 1 | package ch09 2 | 3 | object Ch09 extends App { 4 | 5 | var globalState = 0 6 | 7 | def incGlobal(count: Int): Int = { 8 | globalState += count 9 | globalState 10 | } 11 | 12 | val g1 = incGlobal(10) // g1 == 10 13 | val g2 = incGlobal(10) // g1 == 20 14 | 15 | println(g1, g2) 16 | 17 | def incLocal(count: Int, global: Int): Int = global + count 18 | 19 | val l1 = incLocal(10, 0) // l1 = 10 20 | val l2 = incLocal(10, 0) // l2 = 10 21 | 22 | 23 | println(l1, l2) 24 | } 25 | -------------------------------------------------------------------------------- /Chapter09/src/main/scala/ch09/IdExample.scala: -------------------------------------------------------------------------------- 1 | package ch09 2 | 3 | import scala.language.{higherKinds, implicitConversions} 4 | 5 | object IdExample extends App { 6 | import Monad.Id 7 | import Boat._ 8 | 9 | println(move(go[Id], turn[Id])(boat)) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Chapter09/src/main/scala/ch09/Monad.scala: -------------------------------------------------------------------------------- 1 | package ch09 2 | 3 | import ch07.Monoid 4 | import ch08.Applicative 5 | 6 | import scala.annotation.tailrec 7 | import scala.concurrent.{ExecutionContext, Future} 8 | import scala.language.{higherKinds, implicitConversions} 9 | import scala.util.{Failure, Success, Try} 10 | 11 | 12 | trait Monad[F[_]] extends Applicative[F] { 13 | def flatMap[A, B](a: F[A])(f: A => F[B]): F[B] 14 | 15 | def flatten[A](a: F[F[A]]): F[A] = flatMap(a)(identity) 16 | 17 | override def unit[A](a: => A): F[A] 18 | 19 | override def map[A, B](a: F[A])(f: A => B): F[B] = flatMap(a)(a => unit(f(a))) 20 | 21 | override def apply[A, B](a: F[A])(f: F[A => B]): F[B] = 22 | flatMap(f) { fab: (A => B) => map(a) { a: A => fab(a) }} 23 | } 24 | 25 | object Monad { 26 | def apply[F[_] : Monad]: Monad[F] = implicitly[Monad[F]] 27 | 28 | type Id[A] = A 29 | 30 | implicit val idMonad = new Monad[Id] { 31 | override def unit[A](a: => A): Id[A] = a 32 | override def flatMap[A, B](a: Id[A])(f: A => Id[B]): Id[B] = f(a) 33 | } 34 | 35 | implicit val optionMonad = new Monad[Option] { 36 | override def unit[A](a: => A): Option[A] = Some(a) 37 | 38 | override def flatMap[A, B](a: Option[A])(f: A => Option[B]): Option[B] = a match { 39 | case Some(value) => f(value) 40 | case _ => None 41 | } 42 | } 43 | 44 | implicit val tryMonad = new Monad[Try] { 45 | override def unit[A](a: => A): Try[A] = Success(a) 46 | 47 | override def flatMap[A, B](a: Try[A])(f: A => Try[B]): Try[B] = a match { 48 | case Success(value) => f(value) 49 | case Failure(ex) => Failure(ex) 50 | } 51 | } 52 | 53 | implicit def eitherMonad[L] = new Monad[Either[L, ?]] { 54 | override def unit[A](a: => A): Either[L, A] = Right(a) 55 | 56 | override def flatMap[A, B](a: Either[L, A])(f: A => Either[L, B]): Either[L, B] = a match { 57 | case Right(r) => f(r) 58 | case Left(l) => Left(l) 59 | } 60 | } 61 | 62 | implicit val listMonad = new Monad[List] { 63 | def unit[A](a: => A) = List(a) 64 | 65 | def flatMapNonTailRec[A,B](as: List[A])(f: A => List[B]): List[B] = as match { 66 | case Nil => Nil 67 | case a :: as => f(a) ::: flatMap(as)(f) 68 | } 69 | 70 | def flatMapOkButSlow[A,B](as: List[A])(f: A => List[B]): List[B] = { 71 | @tailrec 72 | def fMap(as: List[A], acc: List[B])(f: A => List[B]): List[B] = as match { 73 | case Nil => acc 74 | case a :: aas => fMap(aas, acc ::: f(a))(f) 75 | } 76 | fMap(as, Nil)(f) 77 | } 78 | 79 | override def flatMap[A,B](as: List[A])(f: A => List[B]): List[B] = as.flatMap(f) 80 | } 81 | 82 | implicit def stateMonad[S] = new Monad[State[S, ?]] { 83 | override def unit[A](a: => A): State[S, A] = State(a) 84 | override def flatMap[A, B](a: State[S, A])(f: A => State[S, B]): State[S, B] = a.compose(f) 85 | } 86 | 87 | implicit def readerMonad[R] = new Monad[Reader[R, ?]] { 88 | override def unit[A](a: => A): Reader[R, A] = Reader(a) 89 | override def flatMap[A, B](a: Reader[R, A])(f: A => Reader[R, B]): Reader[R, B] = a.compose(f) 90 | } 91 | 92 | implicit def writerMonad[W : Monoid] = new Monad[Writer[W, ?]] { 93 | override def unit[A](a: => A): Writer[W, A] = Writer(a) 94 | override def flatMap[A, B](a: Writer[W, A])(f: A => Writer[W, B]): Writer[W, B] = a.compose(f) 95 | } 96 | 97 | // strictly speaking, Future is not a monad because it does not satisfy monadic laws 98 | implicit def futureMonad(implicit ec: ExecutionContext) = new Monad[Future] { 99 | override def unit[A](a: => A): Future[A] = Future(a) 100 | override def flatMap[A, B](a: Future[A])(f: A => Future[B]): Future[B] = 101 | a.flatMap(f) 102 | } 103 | 104 | object lowPriorityImplicits { 105 | implicit class MonadF[A, F[_] : Monad](val value: F[A]) { 106 | private val M = implicitly[Monad[F]] 107 | def unit(a: A) = M.unit(a) 108 | def flatMap[B](fab: A => F[B]): F[B] = M.flatMap(value)(fab) 109 | def map[B](fab: A => B): F[B] = M.map(value)(fab) 110 | } 111 | } 112 | 113 | } 114 | 115 | -------------------------------------------------------------------------------- /Chapter09/src/main/scala/ch09/Reader.scala: -------------------------------------------------------------------------------- 1 | package ch09 2 | 3 | final case class Reader[R, A](run: R => A) { 4 | def compose[B](f: A => Reader[R, B]): Reader[R, B] = Reader { r: R => 5 | f(run(r)).run(r) 6 | } 7 | } 8 | 9 | object Reader { 10 | def apply[R, A](a: => A): Reader[R, A] = Reader(_ => a) 11 | } 12 | 13 | 14 | object ReaderExample extends App { 15 | final case class Limits(speed: Float, angle: Double) 16 | type ReaderLimits[A] = Reader[Limits, A] 17 | 18 | def go(speed: Float, time: Float)(boat: Boat): ReaderLimits[Boat] = Reader(limits => { 19 | val lowSpeed = Math.min(speed, limits.speed) 20 | boat.go(lowSpeed, time) 21 | }) 22 | 23 | def turn(angle: Double)(boat: Boat): ReaderLimits[Boat] = Reader(limits => { 24 | val smallAngle = Math.min(angle, limits.angle) 25 | boat.turn(smallAngle) 26 | }) 27 | 28 | import Monad.readerMonad 29 | import Boat.{move, boat} 30 | 31 | println(move(go, turn)(Reader(boat)).run(Limits(10f, 0.1))) 32 | } 33 | -------------------------------------------------------------------------------- /Chapter09/src/main/scala/ch09/State.scala: -------------------------------------------------------------------------------- 1 | package ch09 2 | 3 | import scala.language.higherKinds 4 | 5 | final case class State[S, A](run: S => (A, S)) { 6 | def compose[B](f: A => State[S, B]): State[S, B] = { 7 | val composedRuns = (s: S) => { 8 | val (a, nextState) = run(s) 9 | f(a).run(nextState) 10 | } 11 | State(composedRuns) 12 | } 13 | } 14 | 15 | object State { 16 | def apply[S, A](a: => A): State[S, A] = State(s => (a, s)) 17 | def get[S]: State[S, S] = State(s => (s, s)) 18 | def set[S](s: => S): State[S, Unit] = State(_ => ((), s)) 19 | } 20 | 21 | object StateExample extends App { 22 | lazy val consumption = 1f 23 | type FuelState = State[Float, Boat] 24 | 25 | def consume(speed: Float, time: Float) = consumption * time * speed 26 | 27 | def go(speed: Float, time: Float)(boat: Boat): FuelState = new State(fuel => { 28 | val newFuel = fuel - consume(speed, time) 29 | (boat.go(speed, time), newFuel) 30 | }) 31 | 32 | def turn(angle: Double)(boat: Boat): FuelState = State(boat.turn(angle)) 33 | 34 | import Boat.boat 35 | 36 | println(boat.go(10, 5).turn(0.5).go(20, 20).turn(-0.1).go(1,1)) 37 | 38 | import Monad.lowPriorityImplicits._ 39 | 40 | def move(boat: Boat) = State[Float, Boat](boat). 41 | flatMap(go(10, 5)). 42 | flatMap(turn(0.5)). 43 | flatMap(go(20,20)). 44 | flatMap(turn(-0.1)). 45 | flatMap{b: Boat => go(1,1)(b)} 46 | 47 | def mv(boat: Boat) = for { 48 | a <- State[Float, Boat](boat) 49 | f1 <- State.get[Float] 50 | _ = logFuelState(f1) 51 | _ <- State.set(Math.min(700, f1)) 52 | b <- go(10,5)(a) 53 | f2 <- State.get[Float]; _ = logFuelState(f2) 54 | c <- turn(0.5)(b) 55 | f3 <- State.get[Float]; _ = logFuelState(f3) 56 | d <- go(20, 20)(c) 57 | f3 <- State.get[Float]; _ = logFuelState(f3) 58 | e <- turn(-0.1)(d) 59 | f3 <- State.get[Float]; _ = logFuelState(f3) 60 | f <- go(1,1)(e) 61 | } yield f 62 | 63 | println(move(boat).run(1000f)) 64 | println(mv(boat).run(1000f)) 65 | 66 | def logFuelState(f: Float) = println(s"Current fuel level is $f") 67 | } 68 | 69 | 70 | object PolymorphicStateExample1 extends App { 71 | import StateExample.consume 72 | type FuelStateBoat = State[Float, Boat] 73 | 74 | def go[M[_]: Monad](speed: Float, time: Float)(boat: Boat): FuelStateBoat = new State((fuel: Float) => { 75 | val newFuel = fuel - consume(speed, time) 76 | (boat.go(speed, time), newFuel) 77 | }) 78 | 79 | def turn(angle: Double)(boat: Boat): FuelStateBoat = State(boat.turn(angle)) 80 | 81 | import Monad.stateMonad 82 | import Monad.lowPriorityImplicits._ 83 | 84 | def move(boat: Boat): State[Float, Boat] = for { 85 | a <- State[Float, Boat](boat) 86 | i <- State.get[Float] 87 | _ <- State.set(Math.min(700, i)) 88 | b <- go(10,5)(a) 89 | c <- turn(0.5)(b) 90 | d <- go(20, 20)(c) 91 | e <- turn(-0.1)(d) 92 | f <- go(1,1)(e) 93 | } yield f 94 | 95 | println(move(Boat.boat).run(1000f)) 96 | } 97 | 98 | object PolymorphicStateExample2 extends App { 99 | 100 | import StateExample.consume 101 | 102 | def go(speed: Float, time: Float)(boat: Boat): State[Float, Boat] = new State((fuel: Float) => { 103 | val newFuel = fuel - consume(speed, time) 104 | (boat.go(speed, time), newFuel) 105 | }) 106 | 107 | import Monad.stateMonad 108 | import Boat.{move, turn, boat} 109 | type FuelState[B] = State[Float, B] 110 | println(move(go, turn[FuelState])(State(boat)).run(1000f)) 111 | } 112 | -------------------------------------------------------------------------------- /Chapter09/src/main/scala/ch09/Writer.scala: -------------------------------------------------------------------------------- 1 | package ch09 2 | 3 | import ch07.Monoid 4 | 5 | final case class Writer[W: Monoid, A](run: (A, W)) { 6 | def compose[B](f: A => Writer[W, B]): Writer[W, B] = Writer { 7 | val (a, w) = run 8 | val (b, ww) = f(a).run 9 | val www = implicitly[Monoid[W]].op(w, ww) 10 | (b, www) 11 | } 12 | } 13 | 14 | object Writer { 15 | def apply[W: Monoid, A](a: => A): Writer[W, A] = Writer((a, implicitly[Monoid[W]].identity)) 16 | } 17 | 18 | 19 | object WriterExample extends App { 20 | 21 | implicit def vectorMonoid[A]: Monoid[Vector[A]] = new Monoid[Vector[A]] { 22 | override def identity: Vector[A] = Vector.empty[A] 23 | override def op(l: Vector[A], r: Vector[A]): Vector[A] = l ++ r 24 | } 25 | 26 | type WriterTracking[A] = Writer[Vector[(Double, Double)], A] 27 | 28 | def go(speed: Float, time: Float)(boat: Boat): WriterTracking[Boat] = 29 | new WriterTracking((boat.go(speed, time), Vector(boat.position))) 30 | 31 | import Monad.writerMonad 32 | import Boat.{move, boat, turn} 33 | 34 | println(move(go, turn[WriterTracking])(Writer(boat)).run) 35 | } 36 | -------------------------------------------------------------------------------- /Chapter09/src/test/scala/ch09/MonadSpecification.scala: -------------------------------------------------------------------------------- 1 | package ch09 2 | 3 | import ch09.Monad.Id 4 | import org.scalacheck._ 5 | import Prop._ 6 | 7 | import scala.language.higherKinds 8 | import scala.util.Try 9 | 10 | object MonadSpecification extends Properties("Monad") { 11 | 12 | def associativity[A, B, C, M[_]](implicit M: Monad[M], 13 | arbMA: Arbitrary[M[A]], 14 | arbMB: Arbitrary[M[B]], 15 | arbMC: Arbitrary[M[C]], 16 | arbB: Arbitrary[B], 17 | arbC: Arbitrary[C], 18 | cogenA: Cogen[A], 19 | cogenB: Cogen[B]): Prop = { 20 | forAll((as: M[A], f: A => M[B], g: B => M[C]) => { 21 | val leftSide = M.flatMap(M.flatMap(as)(f))(g) 22 | val rightSide = M.flatMap(as)(a => M.flatMap(f(a))(g)) 23 | leftSide == rightSide 24 | }) 25 | } 26 | 27 | def id[A, B, M[_]](implicit M: Monad[M], 28 | arbFA: Arbitrary[M[A]], 29 | arbFB: Arbitrary[M[B]], 30 | arbA: Arbitrary[A], 31 | cogenA: Cogen[A]): Prop = { 32 | val leftIdentity = forAll { as: M[A] => 33 | M.flatMap(as)(M.unit(_)) == as 34 | } 35 | val rightIdentity = forAll { (a: A, f: A => M[B]) => 36 | M.flatMap(M.unit(a))(f) == f(a) 37 | } 38 | leftIdentity && rightIdentity 39 | } 40 | 41 | def monad[A, B, C, M[_]](implicit M: Monad[M], 42 | arbMA: Arbitrary[M[A]], 43 | arbMB: Arbitrary[M[B]], 44 | arbMC: Arbitrary[M[C]], 45 | arbA: Arbitrary[A], 46 | arbB: Arbitrary[B], 47 | arbC: Arbitrary[C], 48 | cogenA: Cogen[A], 49 | cogenB: Cogen[B]): Prop = { 50 | id[A, B, M] && associativity[A, B, C, M] 51 | } 52 | 53 | property("Monad[Id] and Int => String, String => Long") = { 54 | monad[Int, String, Long, Id] 55 | } 56 | 57 | property("Monad[Id] and String => Int, Int => Boolean") = { 58 | monad[String, Int, Boolean, Id] 59 | } 60 | 61 | property("Monad[Option] and Int => String, String => Long") = { 62 | monad[Int, String, Long, Option] 63 | } 64 | 65 | property("Monad[Option] and String => Int, Int => Boolean") = { 66 | monad[String, Int, Boolean, Option] 67 | } 68 | 69 | type UnitEither[R] = Either[Unit, R] 70 | 71 | property("Monad[UnitEither[Int]] and Int => String, String => Long") = { 72 | monad[Int, String, Long, UnitEither] 73 | } 74 | 75 | property("Monad[UnitEither[String]] and String => Int, Int => Boolean") = { 76 | monad[String, Int, Boolean, UnitEither] 77 | } 78 | 79 | property("Monad[Try] and Int => String, String => Long") = { 80 | monad[Int, String, Long, Try] 81 | } 82 | 83 | property("Monad[Try] and String => Int, Int => Boolean") = { 84 | monad[String, Int, Boolean, Try] 85 | } 86 | 87 | property("Monad[List] and Int => String, String => Long") = { 88 | monad[Int, String, Long, List] 89 | } 90 | 91 | property("Monad[List] and String => Int, Int => Boolean") = { 92 | monad[String, Int, Boolean, List] 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Chapter10/build.sbt: -------------------------------------------------------------------------------- 1 | organization := "packt" 2 | version := "1.0-SNAPSHOT" 3 | scalaVersion := "2.13.0" 4 | 5 | lazy val ch08 = RootProject(file("../Chapter08")) 6 | 7 | lazy val ch09 = RootProject(file("../Chapter09")) 8 | 9 | lazy val ch10 = project 10 | .in(file(".")) 11 | .settings( 12 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked"), 13 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" withSources () withJavadoc (), 14 | addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.10.3") 15 | ) 16 | .dependsOn(ch08, ch09) 17 | -------------------------------------------------------------------------------- /Chapter10/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /Chapter10/src/main/scala/ch10/Assessments.scala: -------------------------------------------------------------------------------- 1 | package ch10 2 | 3 | import ch09.Monad 4 | import ch09.Monad.lowPriorityImplicits._ 5 | import ch10.Ch10.{Bait, Fish, Line} 6 | import ch10.Ch10OptionTEitherTFutureFishing.goFishing 7 | import ch10.TransformerStacks.{Inner, Stack} 8 | import ch10.Transformers.{EitherT, OptionT, eitherTunit} 9 | 10 | import scala.concurrent.Future 11 | import scala.language.higherKinds 12 | import scala.util.{Failure, Success, Try} 13 | 14 | object Assessments { 15 | 16 | private def noResultTryT[F[_] : Monad, T](ex: Throwable): F[Try[T]] = Monad[F].unit(Failure[T](ex)) 17 | 18 | implicit class TryT[F[_] : Monad, A](val value: F[Try[A]]) { 19 | def compose[B](f: A => TryT[F, B]): TryT[F, B] = { 20 | val result = value.flatMap { 21 | case Failure(ex) => noResultTryT[F, B](ex) 22 | case Success(a) => f(a).value 23 | } 24 | new TryT(result) 25 | } 26 | 27 | def isSuccess: F[Boolean] = Monad[F].map(value)(_.isSuccess) 28 | } 29 | 30 | def tryTunit[F[_] : Monad, A](a: => A) = new TryT(Monad[F].unit(Try(a))) 31 | 32 | implicit def TryTMonad[F[_] : Monad]: Monad[TryT[F, ?]] = new Monad[TryT[F, ?]] { 33 | override def unit[A](a: => A): TryT[F, A] = Monad[F].unit(Monad[Try].unit(a)) 34 | 35 | override def flatMap[A, B](a: TryT[F, A])(f: A => TryT[F, B]): TryT[F, B] = a.compose(f) 36 | } 37 | 38 | 39 | import ch09.Monad.futureMonad 40 | import scala.concurrent.ExecutionContext.Implicits.global 41 | 42 | object Ch10FutureTryFishing extends FishingApi[TryT[Future, ?]] { 43 | 44 | val buyBaitImpl: String => Future[Bait] = ??? 45 | val castLineImpl: Bait => Try[Line] = ??? 46 | val hookFishImpl: Line => Future[Fish] = ??? 47 | 48 | override val buyBait: String => TryT[Future, Bait] = (name: String) => buyBaitImpl(name).map(Try(_)) 49 | override val castLine: Bait => TryT[Future, Line] = castLineImpl.andThen(Future.successful(_)) 50 | override val hookFish: Line => TryT[Future, Fish] = (line: Line) => hookFishImpl(line).map(Try(_)) 51 | 52 | val result: Future[Try[Fish]] = goFishing(tryTunit[Future, String]("Crankbait")).value 53 | 54 | } 55 | 56 | // Transformer Stack 57 | 58 | type Inner[A] = OptionT[Future, A] 59 | type Outer[F[_], A] = EitherT[F, String, A] 60 | type Stack[A] = Outer[Inner, A] 61 | 62 | object Ch10EitherTOptionTFutureFishing extends FishingApi[Stack[?]] { 63 | 64 | val buyBaitImpl: String => Future[Bait] = ??? 65 | val castLineImpl: Bait => Either[String, Line] = ??? 66 | val hookFishImpl: Line => Future[Fish] = ??? 67 | 68 | override val castLine: Bait => Stack[Line] = 69 | (bait: Bait) => new OptionT(Future.successful(Option(castLineImpl(bait)))) 70 | 71 | override val buyBait: String => Stack[Bait] = 72 | (name: String) => new OptionT(buyBaitImpl(name).map(l => Option(Right(l)): Option[Either[String, Bait]])) 73 | 74 | override val hookFish: Line => Stack[Fish] = 75 | (line: Line) => new OptionT(hookFishImpl(line).map(l => Option(Right(l)): Option[Either[String, Fish]])) 76 | 77 | val input: EitherT[Inner, String, String] = eitherTunit[Inner, String, String]("Crankbait") 78 | val outerResult: Inner[Either[String, Fish]] = goFishing(input).value 79 | val innerResult: Future[Option[Either[String, Fish]]] = outerResult.value 80 | 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /Chapter10/src/main/scala/ch10/FreeMonad.scala: -------------------------------------------------------------------------------- 1 | package ch10 2 | 3 | import ch08.Functor 4 | 5 | import scala.annotation.tailrec 6 | import scala.language.higherKinds 7 | 8 | object FreeMonad extends App { 9 | 10 | case class Bait(name: String) extends AnyVal 11 | case class Line(length: Int) extends AnyVal 12 | case class Fish(name: String) extends AnyVal 13 | 14 | sealed trait Action[A] 15 | final case class BuyBait[A](name: String, f: Bait => A) extends Action[A] 16 | final case class CastLine[A](bait: Bait, f: Line => A) extends Action[A] 17 | final case class HookFish[A](line: Line, f: Fish => A) extends Action[A] 18 | 19 | // assessment 20 | final case class ReleaseFish[A](fish: Fish, f: Unit => A) extends Action[A] 21 | 22 | final case class Done[F[_]: Functor, A](a: A) extends Free[F, A] 23 | final case class Join[F[_]: Functor, A](action: F[Free[F, A]]) extends Free[F, A] 24 | 25 | class Free[F[_]: Functor, A] { 26 | def flatMap[B](f: A => Free[F, B]): Free[F, B] = this match { 27 | case Done(a) => f(a) 28 | case Join(a) => Join(implicitly[Functor[F]].map(a)(_.flatMap(f))) 29 | } 30 | def map[B](f: A => B): Free[F, B] = flatMap(a => Done(f(a))) 31 | } 32 | 33 | implicit lazy val actionFunctor: Functor[Action] = new Functor[Action] { 34 | override def map[A, B](in: Action[A])(f: A => B): Action[B] = in match { 35 | case BuyBait(name, a) => BuyBait(name, x => f(a(x))) 36 | case CastLine(bait, a) => CastLine(bait, x => f(a(x))) 37 | case HookFish(line, a) => HookFish(line, x => f(a(x))) 38 | // assessment 39 | case ReleaseFish(fish, a) => ReleaseFish(fish, x => f(a(x))) 40 | } 41 | } 42 | 43 | def buyBait(name: String): Free[Action, Bait] = Join(BuyBait(name, bait => Done(bait))) 44 | def castLine(bait: Bait): Free[Action, Line] = Join(CastLine(bait, line => Done(line))) 45 | def hookFish(line: Line): Free[Action, Fish] = Join(HookFish(line, fish => Done(fish))) 46 | 47 | // assessment 48 | def releaseFish(fish: Fish): Free[Action, Unit] = Join(ReleaseFish(fish, _ => Done(()))) 49 | 50 | def catchFish(baitName: String): Free[Action, _] = for { 51 | bait <- buyBait(baitName) 52 | line <- castLine(bait) 53 | fish <- hookFish(line) 54 | _ <- releaseFish(fish) 55 | } yield () 56 | 57 | def log[A](a: A): Unit = println(a) 58 | 59 | @tailrec 60 | def goFishingLogging[A](actions: Free[Action, A], unit: Unit): A = actions match { 61 | case Join(BuyBait(name, f)) => 62 | goFishingLogging(f(Bait(name)), log(s"Buying bait $name")) 63 | case Join(CastLine(bait, f)) => 64 | goFishingLogging(f(Line(bait.name.length)), log(s"Casting line with ${bait.name}")) 65 | case Join(HookFish(line, f)) => 66 | goFishingLogging(f(Fish("CatFish")), log(s"Hooking fish from ${line.length} feet")) 67 | case Done(fish) => fish 68 | // assessment 69 | case Join(ReleaseFish(fish, f)) => 70 | goFishingLogging(f(()), log(s"Releasing the fish $fish")) 71 | 72 | } 73 | 74 | println(goFishingLogging(catchFish("Crankbait"), ())) 75 | 76 | @tailrec 77 | def goFishingAcc[A](actions: Free[Action, A], log: List[AnyVal]): List[AnyVal] = actions match { 78 | case Join(BuyBait(name, f)) => 79 | val bait = Bait(name) 80 | goFishingAcc(f(bait), bait :: log) 81 | case Join(CastLine(bait, f)) => 82 | val line = Line(bait.name.length) 83 | goFishingAcc(f(line), line :: log) 84 | case Join(HookFish(line, f)) => 85 | val fish = Fish(s"CatFish from ($line)") 86 | goFishingAcc(f(fish), fish :: log) 87 | case Done(_) => log.reverse 88 | // assessment 89 | case Join(ReleaseFish(fish, f)) => 90 | goFishingAcc(f(()), fish.copy(name = fish.name + " released") :: log) 91 | 92 | } 93 | 94 | lazy val log = goFishingAcc(catchFish("Crankbait"), Nil) 95 | println(log) 96 | 97 | } 98 | -------------------------------------------------------------------------------- /Chapter10/src/main/scala/ch10/TransformerStacks.scala: -------------------------------------------------------------------------------- 1 | package ch10 2 | 3 | import ch10.Ch10._ 4 | 5 | import scala.concurrent.Future 6 | import scala.language.higherKinds 7 | import scala.concurrent.ExecutionContext.Implicits.global 8 | import ch10.Transformers._ 9 | import TransformerStacks._ 10 | 11 | object TransformerStacks { 12 | 13 | type Inner[A] = EitherT[Future, String, A] 14 | type Outer[F[_], A] = OptionT[F, A] 15 | type Stack[A] = Outer[Inner, A] 16 | 17 | } 18 | 19 | object Ch10OptionTEitherTFutureFishing extends FishingApi[Stack[?]] { 20 | 21 | val buyBaitImpl: String => Future[Bait] = ??? 22 | val castLineImpl: Bait => Either[String, Line] = ??? 23 | val hookFishImpl: Line => Future[Fish] = ??? 24 | 25 | override val castLine: Bait => Stack[Line] = 26 | (bait: Bait) => new EitherT(Future.successful(castLineImpl(bait).map(Option.apply))) 27 | 28 | override val buyBait: String => Stack[Bait] = 29 | (name: String) => new EitherT(buyBaitImpl(name).map(l => Right(Option(l)): Either[String, Option[Bait]])) 30 | 31 | override val hookFish: Line => Stack[Fish] = 32 | (line: Line) => new EitherT(hookFishImpl(line).map(l => Right(Option(l)): Either[String, Option[Fish]])) 33 | 34 | val input = optionTunit[Inner, String]("Crankbait") 35 | val outerResult: Inner[Option[Fish]] = goFishing(input).value 36 | val innerResult: Future[Either[String, Option[Fish]]] = outerResult.value 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Chapter11/build.sbt: -------------------------------------------------------------------------------- 1 | name := "akka-bakery" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.13.0" 6 | 7 | lazy val akkaVersion = "2.5.25" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-remote" % akkaVersion, 13 | "org.scalatest" %% "scalatest" % "3.0.8" % "test" 14 | ) 15 | -------------------------------------------------------------------------------- /Chapter11/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 -------------------------------------------------------------------------------- /Chapter11/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | actor { 3 | provider = remote 4 | guardian-supervisor-strategy = ch11.GuardianSupervisorStrategyConfigurator 5 | deployment { 6 | /Manager/Boy { 7 | remote = "akka.tcp://Store@127.0.0.1:2553" 8 | } 9 | } 10 | } 11 | remote { 12 | enabled-transports = ["akka.remote.netty.tcp"] 13 | netty.tcp { 14 | hostname = "127.0.0.1" 15 | port = 2552 16 | } 17 | } 18 | } 19 | mixers-dispatcher { 20 | executor = "thread-pool-executor" 21 | type = PinnedDispatcher 22 | } 23 | -------------------------------------------------------------------------------- /Chapter11/src/main/resources/grocery.conf: -------------------------------------------------------------------------------- 1 | include "application" 2 | akka.remote.netty.tcp.port = 2553 3 | -------------------------------------------------------------------------------- /Chapter11/src/main/scala/ch11/Store.scala: -------------------------------------------------------------------------------- 1 | package ch11 2 | 3 | import akka.actor._ 4 | import com.typesafe.config.ConfigFactory 5 | import Manager.ShoppingList 6 | import Mixer.Groceries 7 | 8 | abstract class Store { 9 | val store = ActorSystem("Store", ConfigFactory.load("grocery.conf")) 10 | 11 | val seller: ActorRef = store.actorOf(Props(new Actor { 12 | override def receive: Receive = { 13 | case s: ShoppingList => 14 | ShoppingList.unapply(s).map(Groceries.tupled).foreach(sender() ! _) 15 | } 16 | }), "Seller") 17 | 18 | } 19 | object Store extends Store with App 20 | -------------------------------------------------------------------------------- /Chapter11/src/test/scala/ch11/BakerySpec.scala: -------------------------------------------------------------------------------- 1 | package ch11 2 | 3 | import akka.actor.{ActorSystem, Props} 4 | import akka.testkit.{ImplicitSender, TestKit, TestProbe} 5 | import ch11.Cook.RawCookies 6 | import ch11.Manager.ShoppingList 7 | import ch11.Oven.Cookies 8 | import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} 9 | 10 | import scala.concurrent.duration._ 11 | import scala.language.postfixOps 12 | import scala.util.Random 13 | 14 | class BakerySpec(_system: ActorSystem) 15 | extends TestKit(_system) 16 | with Matchers 17 | with WordSpecLike 18 | with BeforeAndAfterAll 19 | with ImplicitSender { 20 | 21 | def this() = this(ActorSystem("BakerySpec")) 22 | 23 | override def afterAll: Unit = shutdown(system) 24 | 25 | "The boy should" should { 26 | val boyProps = Boy.props(system.actorSelection(testActor.path)) 27 | val boy = system.actorOf(boyProps) 28 | 29 | "forward given ShoppingList to the seller" in { 30 | val list = ShoppingList(0, 0, 0, 0) 31 | boy ! list 32 | within(3 millis, 20 millis) { 33 | expectMsg(list) 34 | lastSender shouldBe testActor 35 | } 36 | } 37 | "ignore other message types" in { 38 | boy ! 'GoHome 39 | expectNoMessage(500 millis) 40 | } 41 | } 42 | "The baker should" should { 43 | val parent = TestProbe() 44 | val baker = parent.childActorOf(Props(classOf[Baker], 0 millis)) 45 | "bake cookies in batches" in { 46 | val count = Random.nextInt(100) 47 | baker ! RawCookies(Oven.size * count) 48 | parent.expectMsgAllOf(List.fill(count)(Cookies(Oven.size)):_*) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Chapter11/src/test/scala/ch11/StoreSpec.scala: -------------------------------------------------------------------------------- 1 | package ch11 2 | 3 | import akka.testkit.TestKit 4 | import ch11.Manager.ShoppingList 5 | import ch11.Mixer.Groceries 6 | import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} 7 | 8 | import scala.language.postfixOps 9 | 10 | class StoreSpec(store: Store) extends TestKit(store.store) 11 | with Matchers with WordSpecLike with BeforeAndAfterAll { 12 | 13 | def this() = this(new Store {}) 14 | 15 | override def afterAll: Unit = shutdown(system) 16 | 17 | "A seller in store" should { 18 | "do nothing for all unexpected message types" in { 19 | store.seller ! 'UnexpectedMessage 20 | expectNoMessage() 21 | } 22 | "return groceries if given a shopping list" in { 23 | store.seller.tell(ShoppingList(1, 1, 1, 1), testActor) 24 | expectMsg(Groceries(1,1,1,1)) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Chapter12/build.sbt: -------------------------------------------------------------------------------- 1 | name := "akka-typed-bakery" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.13.0" 6 | 7 | lazy val akkaVersion = "2.5.25" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-cluster-typed" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-actor-testkit-typed" % akkaVersion % Test, 13 | "org.scalatest" %% "scalatest" % "3.0.8" % Test 14 | ) 15 | -------------------------------------------------------------------------------- /Chapter12/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 -------------------------------------------------------------------------------- /Chapter12/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | mixers-dispatcher { 2 | executor = "thread-pool-executor" 3 | type = PinnedDispatcher 4 | } 5 | akka { 6 | actor.provider = "cluster" 7 | remote { 8 | netty.tcp { 9 | hostname = "127.0.0.1" 10 | port = 2552 11 | } 12 | } 13 | cluster.seed-nodes = [ 14 | "akka.tcp://Typed-Bakery@127.0.0.1:2553", 15 | "akka.tcp://Typed-Bakery@127.0.0.1:2552" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Chapter12/src/main/resources/grocery.conf: -------------------------------------------------------------------------------- 1 | include "application" 2 | akka.remote.netty.tcp.port = 2553 3 | -------------------------------------------------------------------------------- /Chapter12/src/main/scala/ch12/Baker.scala: -------------------------------------------------------------------------------- 1 | package ch12 2 | import akka.actor.typed.{ActorRef, Behavior} 3 | import akka.actor.typed.scaladsl.{Behaviors, StashBuffer} 4 | import ch12.Bakery.{RawCookies, ReadyCookies} 5 | import ch12.Manager.ReceiveReadyCookies 6 | import ch12.Oven.{Extract, Put} 7 | 8 | import scala.concurrent.duration._ 9 | 10 | object Baker { 11 | val DefaultBakingTime: FiniteDuration = 2.seconds 12 | private val TimerKey = 'TimerKey 13 | sealed trait Command 14 | final case class BakeCookies(raw: RawCookies, 15 | sender: ActorRef[Manager.Command]) 16 | extends Command 17 | final case class TooManyCookies(raw: RawCookies) extends Command 18 | final case class CookiesReady(cookies: ReadyCookies) extends Command 19 | final case object CheckOven extends Command 20 | 21 | def turnOvenOn: Behavior[Command] = Behaviors.setup { context => 22 | val oven = context.spawn(Oven.empty, "Oven") 23 | idle(oven) 24 | } 25 | 26 | def idle(oven: ActorRef[Oven.Command]): Behavior[Command] = 27 | Behaviors.receivePartial { 28 | case (context, BakeCookies(rawCookies, manager)) => 29 | oven ! Put(rawCookies.count, context.self) 30 | Behaviors.withTimers { timers => 31 | timers.startSingleTimer(TimerKey, CheckOven, DefaultBakingTime) 32 | baking(oven, manager) 33 | } 34 | } 35 | 36 | def baking(oven: ActorRef[Oven.Command], 37 | manager: ActorRef[Manager.Command]): Behavior[Command] = 38 | Behaviors.setup[Command] { context => 39 | val buffer = StashBuffer[Command](capacity = 100) 40 | 41 | Behaviors.receiveMessage { 42 | case CheckOven => 43 | oven ! Extract(context.self) 44 | Behaviors.same 45 | case CookiesReady(cookies) => 46 | manager ! ReceiveReadyCookies(cookies) 47 | buffer.unstashAll(context, idle(oven)) 48 | case c: TooManyCookies=> 49 | buffer.stash(BakeCookies(c.raw, manager)) 50 | Behaviors.same 51 | case c : BakeCookies => 52 | buffer.stash(c) 53 | Behaviors.same 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Chapter12/src/main/scala/ch12/Bakery.scala: -------------------------------------------------------------------------------- 1 | package ch12 2 | 3 | import akka.actor.typed.ActorSystem 4 | 5 | object Bakery extends App { 6 | final case class Groceries(eggs: Int, flour: Int, sugar: Int, chocolate: Int) 7 | final case class Dough(weight: Int) 8 | final case class RawCookies(count: Int) 9 | final case class ReadyCookies(count: Int) 10 | 11 | val system = ActorSystem(Manager.openBakery, "Typed-Bakery") 12 | } 13 | -------------------------------------------------------------------------------- /Chapter12/src/main/scala/ch12/Boy.scala: -------------------------------------------------------------------------------- 1 | package ch12 2 | 3 | import akka.actor.typed.ActorRef 4 | import akka.actor.typed.scaladsl.Behaviors 5 | import ch12.Shop._ 6 | 7 | object Boy { 8 | final case class GoShopping(shoppingList: ShoppingList, 9 | seller: ActorRef[SellByList], 10 | manager: ActorRef[Manager.Command]) 11 | 12 | val goShopping = Behaviors.receiveMessage[GoShopping] { 13 | case GoShopping(shoppingList, seller, manager) => 14 | seller ! SellByList(shoppingList, manager) 15 | Behaviors.stopped 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter12/src/main/scala/ch12/Chef.scala: -------------------------------------------------------------------------------- 1 | package ch12 2 | 3 | import akka.actor.typed.scaladsl.Behaviors 4 | import akka.actor.typed.{ActorRef, Behavior, DispatcherSelector} 5 | import ch12.Bakery.{Groceries, Dough} 6 | import ch12.Manager.ReceiveDough 7 | 8 | object Chef { 9 | 10 | sealed trait Command 11 | 12 | final case class Mix(g: Groceries, manager: ActorRef[Manager.Command]) 13 | extends Command 14 | 15 | final case class Collect(p: Dough, mixer: ActorRef[Mixer.Mix]) 16 | extends Command 17 | 18 | final case class BrokenMixer(mixer: ActorRef[Mixer.Mix]) extends Command 19 | 20 | def idle(mixerFactory: Behavior[Mixer.Mix]): Behaviors.Receive[Command] = 21 | Behaviors.receivePartial[Command] { 22 | case (context, 23 | mix@Mix(Groceries(eggs, flour, sugar, chocolate), manager)) => 24 | val mixers = for (i <- 1 to eggs) 25 | yield 26 | context.spawn(mixerFactory, 27 | s"Mixer_$i", 28 | DispatcherSelector.fromConfig("mixers-dispatcher")) 29 | mixers.foreach(mixer => context.watchWith(mixer, BrokenMixer(mixer))) 30 | val msg = Groceries(1, flour / eggs, sugar / eggs, chocolate / eggs) 31 | mixers.foreach(_ ! Mixer.Mix(msg, context.self)) 32 | mixing(mixers.toSet, 0, manager, mixerFactory) 33 | } 34 | 35 | def mixing(mixers: Set[ActorRef[Mixer.Mix]], 36 | collected: Int, 37 | manager: ActorRef[Manager.Command], 38 | mixerBuilder: Behavior[Mixer.Mix]): Behaviors.Receive[Command] = { 39 | 40 | def designateBehavior(mixer: ActorRef[Mixer.Mix], doughBuf: Int) = { 41 | val mixersToGo = mixers - mixer 42 | if (mixersToGo.isEmpty) { 43 | manager ! ReceiveDough(Dough(doughBuf)) 44 | idle(mixerBuilder) 45 | } else { 46 | mixing(mixersToGo, doughBuf, manager, mixerBuilder) 47 | } 48 | } 49 | 50 | Behaviors.receivePartial { 51 | case (context, Collect(dough, mixer)) => 52 | val doughBuf = collected + dough.weight 53 | context.stop(mixer) 54 | designateBehavior(mixer, doughBuf) 55 | case (context, BrokenMixer(m)) => 56 | context.log.warning("Broken mixer detected {}", m) 57 | context.self ! Collect(Dough(0), m) 58 | designateBehavior(m, collected) 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Chapter12/src/main/scala/ch12/Cook.scala: -------------------------------------------------------------------------------- 1 | package ch12 2 | 3 | import akka.actor.typed.ActorRef 4 | import akka.actor.typed.scaladsl.Behaviors 5 | import ch12.Bakery.{Dough, RawCookies} 6 | import ch12.Manager.ReceiveRawCookies 7 | 8 | object Cook { 9 | final case class FormCookies(dough: Dough, sender: ActorRef[Manager.Command]) 10 | 11 | val form: Behaviors.Receive[FormCookies] = Behaviors.receiveMessage { 12 | case FormCookies(dough, sender) => 13 | val numberOfCookies = makeCookies(dough.weight) 14 | sender ! ReceiveRawCookies(RawCookies(numberOfCookies)) 15 | form 16 | } 17 | 18 | private val cookieWeight = 60 19 | private def makeCookies(weight: Int): Int = weight / cookieWeight 20 | } 21 | -------------------------------------------------------------------------------- /Chapter12/src/main/scala/ch12/Mixer.scala: -------------------------------------------------------------------------------- 1 | package ch12 2 | 3 | import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} 4 | import akka.actor.typed.scaladsl.Behaviors 5 | import ch12.Bakery.{Groceries, Dough} 6 | import ch12.Chef.Collect 7 | 8 | import scala.concurrent.duration.FiniteDuration 9 | import scala.util.Random 10 | 11 | object Mixer { 12 | class MotorOverheatException extends Exception 13 | class SlowRotationSpeedException extends Exception 14 | class StrongVibrationException extends Exception 15 | 16 | final case class Mix(groceries: Groceries, sender: ActorRef[Collect]) 17 | 18 | def mix(mixTime: FiniteDuration): Behavior[Mix] = Behaviors.receive[Mix] { 19 | case (ctx, Mix(Groceries(eggs, flour, sugar, chocolate), sender)) => 20 | if (Random.nextBoolean()) throw new MotorOverheatException 21 | Thread.sleep(mixTime.toMillis) 22 | sender ! Collect(Dough(eggs * 50 + flour + sugar + chocolate), ctx.self) 23 | Behaviors.stopped 24 | } 25 | 26 | def controlledMix(mixTime: FiniteDuration): Behavior[Mix] = 27 | Behaviors 28 | .supervise( 29 | Behaviors 30 | .supervise(Behaviors 31 | .supervise(mix(mixTime)) 32 | .onFailure[MotorOverheatException](SupervisorStrategy.stop)) 33 | .onFailure[SlowRotationSpeedException](SupervisorStrategy.restart)) 34 | .onFailure[StrongVibrationException](SupervisorStrategy.resume) 35 | } 36 | -------------------------------------------------------------------------------- /Chapter12/src/main/scala/ch12/Oven.scala: -------------------------------------------------------------------------------- 1 | package ch12 2 | 3 | import akka.actor.typed._ 4 | import akka.actor.typed.scaladsl._ 5 | import ch12.Baker.{CookiesReady, TooManyCookies} 6 | import ch12.Bakery.{RawCookies, ReadyCookies} 7 | 8 | object Oven { 9 | val size = 12 10 | 11 | sealed trait Command 12 | final case class Put(rawCookies: Int, sender: ActorRef[Baker.Command]) 13 | extends Command 14 | final case class Extract(sender: ActorRef[Baker.Command]) extends Command 15 | 16 | def empty: Behaviors.Receive[Command] = Behaviors.receiveMessage[Command] { 17 | case Put(rawCookies, sender) => 18 | val (inside, tooMuch) = insert(rawCookies) 19 | tooMuch.foreach(sender ! TooManyCookies(_)) 20 | full(inside) 21 | case Extract(sender) => 22 | sender ! CookiesReady(ReadyCookies(0)) 23 | Behaviors.same 24 | } 25 | 26 | def full(count: Int): Behaviors.Receive[Command] = 27 | Behaviors.receiveMessage[Command] { 28 | case Extract(sender) => 29 | sender ! CookiesReady(ReadyCookies(count)) 30 | empty 31 | case Put(rawCookies, sender) => 32 | sender ! TooManyCookies(RawCookies(rawCookies)) 33 | Behaviors.same 34 | } 35 | 36 | def insert(count: Int): (Int, Option[RawCookies]) = { 37 | val tooMany = math.max(0, count - size) 38 | val cookiesInside = math.min(size, count) 39 | (cookiesInside, Some(tooMany).filter(_ > 0).map(RawCookies)) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Chapter12/src/main/scala/ch12/Shop.scala: -------------------------------------------------------------------------------- 1 | package ch12 2 | 3 | import akka.actor.typed.{ActorRef, ActorSystem, Behavior} 4 | import akka.actor.typed.receptionist.{Receptionist, ServiceKey} 5 | import akka.actor.typed.scaladsl.{ActorContext, Behaviors} 6 | import akka.actor.typed.receptionist.Receptionist._ 7 | import ch12.Bakery.Groceries 8 | import ch12.Manager.ReceiveGroceries 9 | import ch12.Shop.seller 10 | import com.typesafe.config.ConfigFactory 11 | 12 | object Store extends App { 13 | val config = ConfigFactory.load("grocery.conf") 14 | val system = ActorSystem(seller(Shop.systemReceptionist), "Typed-Bakery", config) 15 | } 16 | object Shop { 17 | final case class ShoppingList(eggs: Int, 18 | flour: Int, 19 | sugar: Int, 20 | chocolate: Int) 21 | final case class SellByList(list: ShoppingList, 22 | toWhom: ActorRef[Manager.Command]) 23 | 24 | val SellerKey = ServiceKey[SellByList]("GrocerySeller") 25 | 26 | type ReceptionistFactory = ActorContext[SellByList] => ActorRef[Receptionist.Command] 27 | 28 | val systemReceptionist: ReceptionistFactory = _.system.receptionist 29 | 30 | def seller(receptionist: ReceptionistFactory): Behavior[SellByList] = Behaviors.setup { ctx ⇒ 31 | receptionist(ctx) ! Register(SellerKey, ctx.self) 32 | Behaviors.receiveMessage[SellByList] { 33 | case SellByList(list, toWhom) ⇒ 34 | import list._ 35 | toWhom ! ReceiveGroceries(Groceries(eggs, flour, sugar, chocolate)) 36 | Behaviors.same 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Chapter12/src/test/scala/ch12/BakerySpec.scala: -------------------------------------------------------------------------------- 1 | package ch12 2 | 3 | import akka.actor.testkit.typed.Effect.{NoEffects, Spawned} 4 | import akka.actor.testkit.typed.scaladsl._ 5 | import akka.actor.typed.DispatcherSelector 6 | import ch12.Baker.BakeCookies 7 | import ch12.Bakery.{Groceries, RawCookies} 8 | import ch12.Boy.GoShopping 9 | import ch12.Chef.Mix 10 | import ch12.Oven.Extract 11 | import ch12.Shop.{SellByList, ShoppingList} 12 | import com.typesafe.config.Config 13 | import org.scalatest._ 14 | 15 | import scala.concurrent.duration._ 16 | import scala.language.postfixOps 17 | 18 | class BakerySpec 19 | extends ScalaTestWithActorTestKit(ManualTime.config) 20 | with WordSpecLike 21 | with BeforeAndAfterAll { 22 | 23 | override def afterAll: Unit = super.afterAll() 24 | 25 | "The boy should" should { 26 | "forward given ShoppingList to the seller" in { 27 | val testKit = BehaviorTestKit(Boy.goShopping) 28 | val seller = TestInbox[Shop.SellByList]() 29 | val manager = TestInbox[Manager.Command]() 30 | val list = ShoppingList(1, 1, 1, 1) 31 | testKit.run(GoShopping(list, seller.ref, manager.ref)) 32 | testKit.expectEffect(NoEffects) 33 | seller.expectMessage(SellByList(list, manager.ref)) 34 | assert(!testKit.isAlive) 35 | } 36 | } 37 | "The chef should" should { 38 | "create and destroy mixers as required" in { 39 | // the mixerFactory is needed because behaviour equals method is not implemented correctly 40 | // currently behaviours compared by reference 41 | // therefore we need to have the same behaviour instance for the test to pass 42 | val mixerFactory = Mixer.mix(0 seconds) 43 | val chef = BehaviorTestKit(Chef.idle(mixerFactory)) 44 | val manager = TestInbox[Manager.Command]() 45 | val message = Mix(Groceries(1, 1, 1, 1), manager.ref) 46 | chef.run(message) 47 | chef.expectEffect( 48 | Spawned( 49 | mixerFactory, 50 | "Mixer_1", 51 | DispatcherSelector.fromConfig("mixers-dispatcher") 52 | ) 53 | ) 54 | val expectedByMixer = Mixer.Mix(Groceries(1, 1, 1, 1), chef.ref) 55 | chef.childInbox("Mixer_1").expectMessage(expectedByMixer) 56 | } 57 | } 58 | 59 | val manualTime: ManualTime = ManualTime() 60 | 61 | "The baker should" should { 62 | "bake cookies in batches" in { 63 | val oven = TestProbe[Oven.Command]() 64 | val manager = TestProbe[Manager.Command]() 65 | val baker = spawn(Baker.idle(oven.ref)) 66 | baker ! BakeCookies(RawCookies(1), manager.ref) 67 | oven.expectMessage(Oven.Put(1, baker)) 68 | manualTime.expectNoMessageFor( 69 | Baker.DefaultBakingTime - 1.millisecond, 70 | oven 71 | ) 72 | manualTime.timePasses(Baker.DefaultBakingTime) 73 | oven.expectMessage(Extract(baker)) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Chapter12/src/test/scala/ch12/ShopSpec.scala: -------------------------------------------------------------------------------- 1 | package ch12 2 | 3 | import akka.actor.testkit.typed.Effect.NoEffects 4 | import akka.actor.testkit.typed.scaladsl.{BehaviorTestKit, TestInbox} 5 | import akka.actor.typed.receptionist.Receptionist 6 | import akka.actor.typed.receptionist.Receptionist.Register 7 | import ch12.Bakery.Groceries 8 | import ch12.Manager.ReceiveGroceries 9 | import ch12.Shop.{SellByList, ShoppingList} 10 | import org.scalatest.WordSpec 11 | 12 | import scala.language.postfixOps 13 | 14 | class ShopSpec extends WordSpec { 15 | 16 | "A seller in the shop" should { 17 | "return groceries if given a shopping list" in { 18 | val receptionist = TestInbox[Receptionist.Command]() 19 | val mockReceptionist: Shop.ReceptionistFactory = _ => receptionist.ref 20 | val seller = BehaviorTestKit(Shop.seller(mockReceptionist)) 21 | val inbox = TestInbox[Manager.Command]() 22 | val message = ShoppingList(1,1,1,1) 23 | seller.run(SellByList(message, inbox.ref)) 24 | inbox.expectMessage(ReceiveGroceries(Groceries(1, 1, 1, 1))) 25 | receptionist.expectMessage(Register(Shop.SellerKey, seller.ref)) 26 | seller.expectEffect(NoEffects) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Chapter13/build.sbt: -------------------------------------------------------------------------------- 1 | name := "akka-streams-bakery" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.13.0" 6 | 7 | lazy val akkaVersion = "2.5.25" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.typesafe.akka" %% "akka-stream" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-remote" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % Test, 13 | "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion % Test, 14 | "org.scalatest" %% "scalatest" % "3.0.8" % Test 15 | ) 16 | 17 | parallelExecution in Test := false 18 | -------------------------------------------------------------------------------- /Chapter13/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 -------------------------------------------------------------------------------- /Chapter13/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | actor { 3 | provider = remote 4 | deployment { 5 | /Boy { 6 | remote = "akka.tcp://Store@127.0.0.1:2553" 7 | } 8 | } 9 | } 10 | remote { 11 | enabled-transports = ["akka.remote.netty.tcp"] 12 | netty.tcp { 13 | hostname = "127.0.0.1" 14 | port = 2552 15 | } 16 | } 17 | loggers = ["akka.event.Logging$DefaultLogger"] 18 | # Options: OFF, ERROR, WARNING, INFO, DEBUG 19 | loglevel = "INFO" 20 | } 21 | mixers-dispatcher { 22 | executor = "thread-pool-executor" 23 | type = PinnedDispatcher 24 | } 25 | -------------------------------------------------------------------------------- /Chapter13/src/main/resources/grocery.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | actor.provider = remote 3 | remote { 4 | enabled-transports = ["akka.remote.netty.tcp"] 5 | netty.tcp { 6 | hostname = "127.0.0.1" 7 | port = 2553 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Chapter13/src/main/scala/ch13/Balancer.scala: -------------------------------------------------------------------------------- 1 | package ch13 2 | 3 | import akka.NotUsed 4 | import akka.stream.FlowShape 5 | import akka.stream.scaladsl.{Balance, Flow, GraphDSL, Merge} 6 | 7 | /** 8 | * This balancer implementation is taken from the akka cookbook, 9 | * see https://doc.akka.io/docs/akka/2.5.13/stream/stream-cookbook.html 10 | */ 11 | object Balancer { 12 | def apply[In, Out](subFlow: Flow[In, Out, Any], 13 | count: Int): Flow[In, Out, NotUsed] = { 14 | 15 | Flow.fromGraph(createGraph(subFlow, count)) 16 | } 17 | 18 | import akka.stream.scaladsl.GraphDSL 19 | import GraphDSL.Implicits._ 20 | 21 | def createGraph[Out, In](subFlow: Flow[In, Out, Any], count: Int) = { 22 | val balanceBlock = Balance[In](count, waitForAllDownstreams = false) 23 | val mergeBlock = Merge[Out](count, eagerComplete = false) 24 | GraphDSL.create() { implicit builder ⇒ 25 | val balancer = builder.add(balanceBlock) 26 | val merge = builder.add(mergeBlock) 27 | 28 | for (_ ← 1 to count) balancer ~> subFlow ~> merge 29 | 30 | FlowShape(balancer.in, merge.out) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Chapter13/src/main/scala/ch13/Store.scala: -------------------------------------------------------------------------------- 1 | package ch13 2 | 3 | import akka.actor._ 4 | import Bakery.Groceries 5 | import com.typesafe.config.ConfigFactory 6 | 7 | object Store extends App { 8 | final case class ShoppingList(eggs: Int, 9 | flour: Int, 10 | sugar: Int, 11 | chocolate: Int) 12 | lazy val store = ActorSystem("Store", ConfigFactory.load("grocery.conf")) 13 | 14 | val seller: ActorRef = store.actorOf(Props(new Actor { 15 | override def receive: Receive = { 16 | case s: ShoppingList => 17 | ShoppingList.unapply(s).map(Groceries.tupled).foreach(sender() ! _) 18 | } 19 | }), "Seller") 20 | } 21 | -------------------------------------------------------------------------------- /Chapter13/src/test/scala/ch13/BakerySpec.scala: -------------------------------------------------------------------------------- 1 | package ch13 2 | 3 | import akka.NotUsed 4 | import akka.actor.ActorSystem 5 | import akka.stream.scaladsl.{Keep, Sink, Source} 6 | import akka.stream.testkit.{TestPublisher, TestSubscriber} 7 | import akka.stream.testkit.scaladsl.{TestSink, TestSource} 8 | import akka.stream.{ActorMaterializer, Materializer} 9 | import akka.testkit.TestProbe 10 | import ch13.Bakery.{Dough, Groceries, RawCookies, ReadyCookies} 11 | import ch13.Store.ShoppingList 12 | import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} 13 | 14 | import scala.concurrent.duration._ 15 | import scala.concurrent.{Await, ExecutionContext, Future} 16 | 17 | class BakerySpecPlain 18 | extends Matchers 19 | with WordSpecLike 20 | with BeforeAndAfterAll { 21 | 22 | implicit val as: ActorSystem = ActorSystem("test") 23 | implicit val mat: Materializer = ActorMaterializer() 24 | 25 | "manager source" should { 26 | "emit shopping lists as needed" in { 27 | val future: Future[Seq[ShoppingList]] = 28 | Manager.manager.take(100).runWith(Sink.seq) 29 | val result: Seq[Store.ShoppingList] = Await.result(future, 1.seconds) 30 | assert(result.size == 100) 31 | } 32 | } 33 | 34 | "cook flow" should { 35 | "convert flow elements one-to-one" in { 36 | val source = Source.repeat(Dough(100)).take(1000) 37 | val sink = Sink.seq[RawCookies] 38 | val future: Future[Seq[RawCookies]] = 39 | source.via(Cook.formFlow).runWith(sink) 40 | val result: Seq[RawCookies] = Await.result(future, 1.seconds) 41 | assert(result.size == 1000) 42 | assert(result.forall(_.count == 2)) 43 | } 44 | } 45 | 46 | override def afterAll(): Unit = { 47 | as.terminate() 48 | super.afterAll() 49 | } 50 | } 51 | 52 | class BakerySpecTestKit 53 | extends Matchers 54 | with WordSpecLike 55 | with BeforeAndAfterAll { 56 | 57 | implicit val as: ActorSystem = ActorSystem("test") 58 | implicit val mat: Materializer = ActorMaterializer() 59 | implicit val ec: ExecutionContext = as.dispatcher 60 | 61 | "the boy flow" should { 62 | "lookup a remote seller and communicate with it" in { 63 | val probe = TestProbe() 64 | val source = Manager.manager.take(1) 65 | val sink = Sink.actorRef[Groceries](probe.ref, NotUsed) 66 | source.via(Boy.shopFlow).runWith(sink) 67 | probe.expectMsgType[Groceries] 68 | } 69 | } 70 | 71 | override def afterAll(): Unit = { 72 | as.terminate() 73 | super.afterAll() 74 | } 75 | } 76 | 77 | class BakerySpecTestProbe 78 | extends Matchers 79 | with WordSpecLike 80 | with BeforeAndAfterAll { 81 | 82 | implicit val as: ActorSystem = ActorSystem("test") 83 | implicit val mat: Materializer = ActorMaterializer() 84 | implicit val ec: ExecutionContext = as.dispatcher 85 | 86 | "the whole flow" should { 87 | "produce cookies" in { 88 | val testSink = TestSink.probe[ReadyCookies] 89 | val source = TestSource.probe[ShoppingList] 90 | val (publisher: TestPublisher.Probe[ShoppingList], 91 | subscriber: TestSubscriber.Probe[ReadyCookies]) = 92 | source.via(Bakery.flow).toMat(testSink)(Keep.both).run() 93 | subscriber.request(10) 94 | publisher.sendNext(ShoppingList(30, 1000, 100, 100)) 95 | subscriber.expectNext(140.seconds, ReadyCookies(12)) 96 | subscriber.expectNext(140.seconds, ReadyCookies(12)) 97 | } 98 | } 99 | 100 | override def afterAll(): Unit = { 101 | as.terminate() 102 | super.afterAll() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | slick_db_url = "jdbc:h2:file:./ch14;DB_CLOSE_DELAY=-1" 2 | 3 | include "default" 4 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/main/resources/db/migration/V1__default_table.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS PUBLIC."journal"; 2 | 3 | CREATE TABLE IF NOT EXISTS PUBLIC."journal" ( 4 | "ordering" BIGINT AUTO_INCREMENT, 5 | "persistence_id" VARCHAR(255) NOT NULL, 6 | "sequence_number" BIGINT NOT NULL, 7 | "deleted" BOOLEAN DEFAULT FALSE, 8 | "tags" VARCHAR(255) DEFAULT NULL, 9 | "message" BYTEA NOT NULL, 10 | PRIMARY KEY("persistence_id", "sequence_number") 11 | ); 12 | 13 | CREATE UNIQUE INDEX "journal_ordering_idx" ON PUBLIC."journal"("ordering"); 14 | 15 | DROP TABLE IF EXISTS PUBLIC."snapshot"; 16 | 17 | CREATE TABLE IF NOT EXISTS PUBLIC."snapshot" ( 18 | "persistence_id" VARCHAR(255) NOT NULL, 19 | "sequence_number" BIGINT NOT NULL, 20 | "created" BIGINT NOT NULL, 21 | "snapshot" BYTEA NOT NULL, 22 | PRIMARY KEY("persistence_id", "sequence_number") 23 | ); 24 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/main/resources/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | host = "0.0.0.0" 3 | port = 8080 4 | } 5 | 6 | timeout = 10 seconds 7 | 8 | slick { 9 | profile = "slick.jdbc.H2Profile$" 10 | db { 11 | url = ${slick_db_url} 12 | user = "sa" 13 | password = "" 14 | driver = "org.h2.Driver" 15 | numThreads = 5 16 | maxConnections = 5 17 | minConnections = 1 18 | } 19 | } 20 | 21 | akka { 22 | persistence { 23 | journal { 24 | plugin = "jdbc-journal" 25 | auto-start-journals = ["jdbc-journal"] 26 | } 27 | snapshot-store { 28 | plugin = "jdbc-snapshot-store" 29 | auto-start-snapshot-stores = ["jdbc-snapshot-store"] 30 | } 31 | } 32 | actor { 33 | serializers { 34 | serializer = "ch14.EventSerializer" 35 | } 36 | serialization-bindings { 37 | "stamina.Persistable" = serializer 38 | } 39 | } 40 | 41 | loggers = ["akka.event.slf4j.Slf4jLogger"] 42 | loglevel = "DEBUG" 43 | logging-filter = "akka.event.slf4j.Slf4jLoggingFilter" 44 | } 45 | 46 | 47 | jdbc-journal { 48 | slick = ${slick} 49 | } 50 | 51 | jdbc-snapshot-store { 52 | slick = ${slick} 53 | } 54 | 55 | jdbc-read-journal { 56 | slick = ${slick} 57 | } 58 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/main/scala/ch14/DB.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import org.flywaydb.core.Flyway 4 | 5 | object DB { 6 | def initialize(cfg: DBConfig): Int = { 7 | val flyWay = new Flyway() 8 | flyWay.setDataSource(cfg.url, cfg.user, cfg.password) 9 | flyWay.migrate() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/main/scala/ch14/InventoryActor.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import akka.actor.{Actor, ActorLogging, Props} 4 | import akka.persistence.{PersistentActor, RecoveryCompleted, SnapshotOffer} 5 | import ch14.Commands.{GetArticle, GetInventory} 6 | 7 | object InventoryActor { 8 | def props: Props = Props[InventoryActor] 9 | val persistenceId = "Inventory" 10 | } 11 | 12 | class InventoryActor extends PersistentActor with Actor with ActorLogging { 13 | 14 | override def persistenceId: String = InventoryActor.persistenceId 15 | 16 | private var inventory: Inventory = Inventory(Map.empty) 17 | 18 | override def receiveRecover: Receive = { 19 | case event: Event => inventory = inventory.update(event) 20 | case SnapshotOffer(_, snapshot: Inventory) => inventory = snapshot 21 | case RecoveryCompleted => saveSnapshot(inventory) 22 | } 23 | 24 | override def receiveCommand: Receive = { 25 | case GetInventory => 26 | sender() ! inventory 27 | 28 | case GetArticle(name) => 29 | sender() ! Inventory(inventory.state.filter(_._1 == name)) 30 | 31 | case cmd: Command => 32 | inventory.canUpdate(cmd) match { 33 | case None => 34 | sender() ! None 35 | case Some(event) => 36 | persistAsync(event) { ev => 37 | inventory = inventory.update(ev) 38 | sender() ! Some(ev) 39 | } 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/main/scala/ch14/JsonSupport.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport 4 | import ch14.Commands._ 5 | import ch14.Events._ 6 | import spray.json.{DefaultJsonProtocol, RootJsonFormat} 7 | import stamina.StaminaAkkaSerializer 8 | import stamina.json._ 9 | 10 | trait JsonSupport extends SprayJsonSupport { 11 | 12 | import DefaultJsonProtocol._ 13 | 14 | type RJF[T] = RootJsonFormat[T] 15 | 16 | implicit val createArticleJF: RJF[CreateArticle] = jsonFormat2(CreateArticle) 17 | implicit val deleteArticleJF: RJF[DeleteArticle] = jsonFormat1(DeleteArticle) 18 | implicit val purchaseJF: RJF[PurchaseArticles] = jsonFormat1(PurchaseArticles) 19 | implicit val restockJF: RJF[RestockArticles] = jsonFormat1(RestockArticles) 20 | 21 | implicit val createdJF: RJF[ArticleCreated] = jsonFormat2(ArticleCreated) 22 | implicit val deletedJF: RJF[ArticleDeleted] = jsonFormat1(ArticleDeleted) 23 | implicit val pJF: RJF[ArticlesPurchased] = jsonFormat1(ArticlesPurchased) 24 | implicit val reJF: RJF[ArticlesRestocked] = jsonFormat1(ArticlesRestocked) 25 | 26 | implicit val invJF: RJF[Inventory] = jsonFormat1(Inventory) 27 | 28 | } 29 | 30 | object PersistenceSupport extends JsonSupport { 31 | val v1createdP = persister[ArticleCreated]("article-created") 32 | val v1deletedP = persister[ArticleDeleted]("article-deleted") 33 | val v1purchasedP = persister[ArticlesPurchased]("articles-purchased") 34 | val v1restockedP = persister[ArticlesRestocked]("articles-restocked") 35 | val v1inventoryP = persister[Inventory]("inventory") 36 | } 37 | 38 | import PersistenceSupport._ 39 | 40 | class EventSerializer 41 | extends StaminaAkkaSerializer(v1createdP, 42 | v1deletedP, 43 | v1purchasedP, 44 | v1restockedP, 45 | v1inventoryP) 46 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/main/scala/ch14/Routes.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import akka.actor.{ActorRef, ActorSystem} 4 | import akka.http.scaladsl.model.{HttpResponse, StatusCodes} 5 | import akka.http.scaladsl.server.Directives._ 6 | import akka.http.scaladsl.server.Route 7 | import akka.http.scaladsl.server.directives.MethodDirectives.{delete, get, post} 8 | import akka.http.scaladsl.server.directives.PathDirectives.path 9 | import akka.http.scaladsl.server.directives.RouteDirectives.complete 10 | import akka.pattern.ask 11 | import akka.util.Timeout 12 | import ch14.Commands._ 13 | import ch14.Events.{ 14 | ArticleCreated, 15 | ArticleDeleted, 16 | ArticlesPurchased, 17 | ArticlesRestocked 18 | } 19 | 20 | import scala.concurrent.{ExecutionContext, Future} 21 | 22 | trait Routes extends JsonSupport { 23 | implicit def system: ActorSystem 24 | def inventory: ActorRef 25 | def config: Config 26 | 27 | implicit lazy val timeout: Timeout = config.timeout 28 | implicit lazy val ec: ExecutionContext = system.dispatcher 29 | 30 | lazy val articlesRoutes: Route = 31 | pathPrefix("articles") { 32 | concat( 33 | path(Segment) { name => 34 | concat( 35 | post { 36 | val changedInventory: Future[Option[ArticleCreated]] = 37 | (inventory ? CreateArticle(name, 0)) 38 | .mapTo[Option[ArticleCreated]] 39 | onSuccess(changedInventory) { 40 | case None => complete(StatusCodes.Conflict) 41 | case Some(event) => complete(StatusCodes.Created, event) 42 | } 43 | }, 44 | delete { 45 | val changedInventory: Future[Option[ArticleDeleted]] = 46 | (inventory ? DeleteArticle(name)).mapTo[Option[ArticleDeleted]] 47 | rejectEmptyResponse { 48 | complete(changedInventory) 49 | } 50 | }, 51 | get { 52 | complete((inventory ? GetArticle(name)).mapTo[Inventory]) 53 | } 54 | ) 55 | } 56 | ) 57 | } 58 | 59 | lazy val inventoryRoutes: Route = 60 | path("inventory") { 61 | get { 62 | complete((inventory ? GetInventory).mapTo[Inventory]) 63 | } 64 | } ~ 65 | path("purchase") { 66 | post { 67 | entity(as[PurchaseArticles]) { order => 68 | val response: Future[Option[ArticlesPurchased]] = 69 | (inventory ? order).mapTo[Option[ArticlesPurchased]] 70 | onSuccess(response) { 71 | case None => complete(StatusCodes.Conflict) 72 | case Some(event) => complete(event) 73 | } 74 | } 75 | } 76 | } ~ 77 | path("restock") { 78 | post { 79 | entity(as[RestockArticles]) { stock => 80 | val response: Future[Option[ArticlesRestocked]] = 81 | (inventory ? stock).mapTo[Option[ArticlesRestocked]] 82 | complete(response) 83 | } 84 | } 85 | } 86 | 87 | 88 | lazy val routes: Route = articlesRoutes ~ inventoryRoutes 89 | 90 | } 91 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/main/scala/ch14/Server.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import scala.concurrent.Await 4 | import scala.concurrent.duration.Duration 5 | import akka.actor.{ActorRef, ActorSystem} 6 | import akka.http.scaladsl.Http 7 | import akka.stream.ActorMaterializer 8 | 9 | object Server extends App with Routes with JsonSupport { 10 | 11 | val config = Config.load() 12 | 13 | implicit val system: ActorSystem = ActorSystem("ch14") 14 | implicit val materializer: ActorMaterializer = ActorMaterializer() 15 | 16 | lazy val inventory: ActorRef = system.actorOf(InventoryActor.props, InventoryActor.persistenceId) 17 | 18 | DB.initialize(config.database) 19 | Http().bindAndHandle(routes, config.server.host, config.server.port) 20 | Await.result(system.whenTerminated, Duration.Inf) 21 | } 22 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/main/scala/ch14/config.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import akka.util.Timeout 6 | import com.typesafe.config 7 | import com.typesafe.config.{ConfigFactory, ConfigResolveOptions} 8 | 9 | case class ServerConfig(host: String, port: Int) 10 | 11 | case class DBConfig(driver: String, url: String, user: String, password: String) 12 | 13 | case class Config(server: ServerConfig, database: DBConfig, timeout: Timeout) 14 | 15 | object Config { 16 | def load(): Config = { 17 | val c = ConfigFactory.parseResources("application.conf").resolve() 18 | val srv = c.getConfig("server") 19 | val serverConfig = ServerConfig(srv.getString("host"), srv.getInt("port")) 20 | val db = c.getConfig("slick.db") 21 | val dbConfig = DBConfig(db.getString("driver"), 22 | db.getString("url"), 23 | db.getString("user"), 24 | db.getString("password")) 25 | val d = c.getDuration("timeout") 26 | val timeout = new Timeout(d.toMillis, TimeUnit.MILLISECONDS) 27 | Config(serverConfig, dbConfig, timeout) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/main/scala/ch14/model.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import ch14.Commands.{ 4 | CreateArticle, 5 | DeleteArticle, 6 | PurchaseArticles, 7 | RestockArticles 8 | } 9 | import ch14.Events.{ 10 | ArticleCreated, 11 | ArticleDeleted, 12 | ArticlesPurchased, 13 | ArticlesRestocked 14 | } 15 | import stamina.Persistable 16 | 17 | sealed trait Event extends Persistable 18 | 19 | object Events { 20 | final case class ArticleCreated(name: String, count: Int) extends Event 21 | final case class ArticleDeleted(name: String) extends Event 22 | final case class ArticlesPurchased(order: Map[String, Int]) extends Event 23 | final case class ArticlesRestocked(stock: Map[String, Int]) extends Event 24 | } 25 | 26 | sealed trait Command 27 | sealed trait Query 28 | 29 | object Commands { 30 | final case class CreateArticle(name: String, count: Int) extends Command 31 | final case class DeleteArticle(name: String) extends Command 32 | final case class PurchaseArticles(order: Map[String, Int]) extends Command 33 | final case class RestockArticles(stock: Map[String, Int]) extends Command 34 | final case object GetInventory extends Query 35 | final case class GetArticle(name: String) extends Query 36 | 37 | } 38 | 39 | final case class Inventory(state: Map[String, Int]) extends Persistable { 40 | def plus(name: String, count: Int): Option[Inventory] = 41 | state.get(name) match { 42 | case None => Some(Inventory(state.updated(name, count))) 43 | case _ => None 44 | } 45 | def minus(name: String): Option[Inventory] = 46 | if (state.contains(name)) 47 | Some(Inventory(state.filterKeys(k => !(k == name)))) 48 | else None 49 | def add(o: Map[String, Int]): Inventory = { 50 | val newState = state.foldLeft(Map.empty[String, Int]) { 51 | case (acc, (k, v)) => 52 | acc.updated(k, v + o.getOrElse(k, 0)) 53 | } 54 | Inventory(newState) 55 | } 56 | 57 | def canUpdate(cmd: Command): Option[Event] = cmd match { 58 | case CreateArticle(name, cnt) => 59 | plus(name, cnt).map(_ => ArticleCreated(name, cnt)) 60 | case DeleteArticle(name) => minus(name).map(_ => ArticleDeleted(name)) 61 | case PurchaseArticles(order) => 62 | val updated = add(order.mapValues(_ * -1)) 63 | if (updated.state.forall(_._2 >= 0)) Some(ArticlesPurchased(order)) else None 64 | case RestockArticles(stock) => Some(ArticlesRestocked(stock)) 65 | } 66 | def update(event: Event): Inventory = event match { 67 | case ArticleCreated(name, cnt) => plus(name, cnt).get 68 | case ArticleDeleted(name) => minus(name).get 69 | case ArticlesPurchased(order) => add(order.mapValues(_ * -1)) 70 | case ArticlesRestocked(stock) => add(stock) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/test/resources/application.conf: -------------------------------------------------------------------------------- 1 | slick_db_url = "jdbc:h2:mem:test;DATABASE_TO_UPPER=false;;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1" 2 | 3 | include "default" 4 | -------------------------------------------------------------------------------- /Chapter14/akka-http/src/test/scala/ch14/RoutesSpec.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import akka.actor.ActorRef 4 | import akka.http.scaladsl.marshalling.Marshal 5 | import akka.http.scaladsl.model._ 6 | import akka.http.scaladsl.server.Route 7 | import akka.http.scaladsl.testkit.ScalatestRouteTest 8 | import ch14.Commands.{PurchaseArticles, RestockArticles} 9 | import org.scalatest.concurrent.ScalaFutures 10 | import org.scalatest.{Matchers, WordSpec} 11 | 12 | import scala.concurrent.duration._ 13 | 14 | class RoutesSpec 15 | extends WordSpec 16 | with Matchers 17 | with ScalaFutures 18 | with ScalatestRouteTest 19 | with Routes { 20 | 21 | override lazy val config: Config = Config.load() 22 | 23 | DB.initialize(config.database) 24 | 25 | override lazy val inventory: ActorRef = 26 | system.actorOf(InventoryActor.props, "inventory") 27 | 28 | "Routes" should { 29 | "return no articles in the beginning" in { 30 | val request = HttpRequest(uri = "/inventory") 31 | implicit val timeout: Duration = 3.seconds 32 | request ~> routes ~> check { 33 | status shouldBe StatusCodes.OK 34 | contentType shouldBe ContentTypes.`application/json` 35 | entityAs[String] shouldBe """{"state":{}}""" 36 | } 37 | } 38 | "be able to add article (POST /articles/eggs)" in { 39 | val request = Post("/articles/eggs") 40 | request ~> routes ~> check { 41 | status shouldBe StatusCodes.Created 42 | contentType shouldBe ContentTypes.`application/json` 43 | entityAs[String] shouldBe """{"name":"eggs","count":0}""" 44 | } 45 | } 46 | "not be able to delete article (delete /articles/no)" in { 47 | val request = Delete("/articles/no-such-article") 48 | request ~> Route.seal(routes) ~> check { 49 | status shouldBe StatusCodes.NotFound 50 | } 51 | } 52 | "not be able to add article twice (POST /articles/eggs)" in { 53 | val request = Post("/articles/eggs") 54 | request ~> routes ~> check { 55 | status shouldBe StatusCodes.Conflict 56 | } 57 | } 58 | "be able to restock articles (POST /restock)" in { 59 | val restock = RestockArticles(Map("eggs" -> 10, "chocolate" -> 20)) 60 | val entity = Marshal(restock).to[MessageEntity].futureValue // futureValue is from ScalaFutures 61 | val request = Post("/restock").withEntity(entity) 62 | request ~> routes ~> check { 63 | status shouldBe StatusCodes.OK 64 | contentType shouldBe ContentTypes.`application/json` 65 | entityAs[String] shouldBe """{"stock":{"eggs":10,"chocolate":20}}""" 66 | } 67 | } 68 | "be able to purchase articles (POST /purchase)" in { 69 | val restock = PurchaseArticles(Map("eggs" -> 5, "chocolate" -> 10)) 70 | val entity = Marshal(restock).to[MessageEntity].futureValue // futureValue is from ScalaFutures 71 | val request = Post("/purchase").withEntity(entity) 72 | request ~> routes ~> check { 73 | status shouldBe StatusCodes.OK 74 | contentType shouldBe ContentTypes.`application/json` 75 | entityAs[String] shouldBe """{"order":{"eggs":5,"chocolate":10}}""" 76 | } 77 | } 78 | "not be able to purchase articles (POST /purchase)" in { 79 | val restock = PurchaseArticles(Map("eggs" -> 50, "chocolate" -> 10)) 80 | val entity = Marshal(restock).to[MessageEntity].futureValue // futureValue is from ScalaFutures 81 | val request = Post("/purchase").withEntity(entity) 82 | request ~> routes ~> check { 83 | status shouldBe StatusCodes.Conflict 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Chapter14/build.sbt: -------------------------------------------------------------------------------- 1 | 2 | 3 | lazy val http4sVersion = "0.18.9" 4 | lazy val doobieVersion = "0.5.3" 5 | lazy val h2Version = "1.4.197" 6 | lazy val flywayVersion = "5.1.4" 7 | lazy val circeVersion = "0.9.3" 8 | lazy val pureConfigVersion = "0.9.1" 9 | lazy val logbackVersion = "1.2.3" 10 | lazy val catsVersion = "1.1.0" 11 | lazy val scalaTestVersion = "3.0.5" 12 | lazy val scalaMockVersion = "4.1.0" 13 | lazy val akkaHttpVersion = "10.1.3" 14 | lazy val akkaVersion = "2.5.14" 15 | lazy val akkaPersistenceVersion = "3.4.0" 16 | lazy val staminaVersion = "0.1.4" 17 | 18 | lazy val akkaHttp = (project in file("akka-http")). 19 | settings( 20 | inThisBuild(List( 21 | organization := "Packt", 22 | version := "1.0-SNAPSHOT", 23 | scalaVersion := "2.12.6" 24 | )), 25 | name := "akka-http", 26 | libraryDependencies ++= Seq( 27 | "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, 28 | "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion, 29 | "com.typesafe.akka" %% "akka-stream" % akkaVersion, 30 | "com.typesafe.akka" %% "akka-persistence" % akkaVersion, 31 | "com.typesafe.akka" %% "akka-persistence-query" % akkaVersion, 32 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 33 | 34 | "com.github.dnvriend" %% "akka-persistence-jdbc" % akkaPersistenceVersion, 35 | "com.scalapenos" %% "stamina-json" % staminaVersion, 36 | "com.h2database" % "h2" % h2Version, 37 | "org.flywaydb" % "flyway-core" % flywayVersion, 38 | "ch.qos.logback" % "logback-classic" % logbackVersion, 39 | 40 | "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test, 41 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % Test, 42 | "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion % Test, 43 | "org.scalatest" %% "scalatest" % scalaTestVersion % Test 44 | ) 45 | ) 46 | 47 | lazy val http4s = (project in file("http4s-doobie")) 48 | .configs(IntegrationTest) 49 | .settings( 50 | inThisBuild(List( 51 | organization := "Packt", 52 | version := "1.0-SNAPSHOT", 53 | scalaVersion := "2.12.6" 54 | )), 55 | name := "http4s-doobie", 56 | Defaults.itSettings, 57 | libraryDependencies ++= Seq( 58 | "org.http4s" %% "http4s-blaze-server" % http4sVersion, 59 | "org.http4s" %% "http4s-circe" % http4sVersion, 60 | "org.http4s" %% "http4s-dsl" % http4sVersion, 61 | "org.tpolecat" %% "doobie-core" % doobieVersion, 62 | "org.tpolecat" %% "doobie-h2" % doobieVersion, 63 | "org.tpolecat" %% "doobie-hikari" % doobieVersion, 64 | "com.h2database" % "h2" % h2Version, 65 | "org.flywaydb" % "flyway-core" % flywayVersion, 66 | "io.circe" %% "circe-generic" % circeVersion, 67 | "com.github.pureconfig" %% "pureconfig" % pureConfigVersion, 68 | "ch.qos.logback" % "logback-classic" % logbackVersion, 69 | "org.typelevel" %% "cats-core" % catsVersion, 70 | 71 | "org.http4s" %% "http4s-blaze-client" % http4sVersion % "it,test", 72 | "io.circe" %% "circe-literal" % circeVersion % "it,test", 73 | "io.circe" %% "circe-optics" % circeVersion % "it", 74 | "org.scalatest" %% "scalatest" % scalaTestVersion % "it,test", 75 | "org.scalamock" %% "scalamock" % scalaMockVersion % Test 76 | ) 77 | ) 78 | 79 | lazy val root = (project in file(".")).aggregate(akkaHttp, http4s) 80 | -------------------------------------------------------------------------------- /Chapter14/ch14.mv.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Scala-Programming/3349fd21aeeb193b13d8366d4b94c8bc127277c1/Chapter14/ch14.mv.db -------------------------------------------------------------------------------- /Chapter14/http4s-doobie/src/it/resources/test.conf: -------------------------------------------------------------------------------- 1 | include "application.conf" 2 | 3 | server.host = "127.0.0.1" 4 | database.url = "jdbc:h2:mem:todo_test;MODE=PostgreSQL;DB_CLOSE_DELAY=-1" 5 | -------------------------------------------------------------------------------- /Chapter14/http4s-doobie/src/it/scala/ServerSpec.scala: -------------------------------------------------------------------------------- 1 | import ch14.{Config, Server} 2 | import cats.effect.IO 3 | import cats.implicits._ 4 | import io.circe.Json 5 | import io.circe.literal._ 6 | import org.http4s.circe._ 7 | import org.http4s.client.blaze.Http1Client 8 | import org.http4s.{Method, Request, Status, Uri} 9 | import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpec} 10 | import org.http4s.server.{Server => Http4sServer} 11 | 12 | class ServerSpec extends WordSpec with Matchers with BeforeAndAfterAll { 13 | private lazy val client = Http1Client[IO]().unsafeRunSync() 14 | 15 | private lazy val configIO = Config.load("test.conf") 16 | private lazy val config = configIO.unsafeRunSync() 17 | 18 | private lazy val rootUrl = s"http://${config.server.host}:${config.server.port}" 19 | 20 | private val server: Option[Http4sServer[IO]] = (for { 21 | builder <- Server.createServer(configIO) 22 | } yield builder.start.unsafeRunSync()).compile.last.unsafeRunSync() 23 | 24 | 25 | override def afterAll(): Unit = { 26 | client.shutdown.unsafeRunSync() 27 | server.foreach(_.shutdown.unsafeRunSync()) 28 | } 29 | 30 | "The server" should { 31 | "get an empty inventory" in { 32 | val json = client.expect[Json](s"$rootUrl/inventory").unsafeRunSync() 33 | json shouldBe json"""{}""" 34 | } 35 | "create articles" in { 36 | val eggs = Request[IO](method = Method.POST, uri = Uri.unsafeFromString(s"$rootUrl/articles/eggs")) 37 | client.status(eggs).unsafeRunSync() shouldBe Status.NoContent 38 | val chocolate = Request[IO](method = Method.POST, uri = Uri.unsafeFromString(s"$rootUrl/articles/chocolate")) 39 | client.status(chocolate).unsafeRunSync() shouldBe Status.NoContent 40 | val json = client.expect[Json](s"$rootUrl/inventory").unsafeRunSync() 41 | json shouldBe json"""{"eggs" : 0,"chocolate" : 0}""" 42 | } 43 | "update inventory" in { 44 | val restock = Request[IO](method = Method.POST, uri = Uri.unsafeFromString(s"$rootUrl/restock")).withBody(json"""{ "inventory" : { "eggs": 10, "chocolate": 20 }}""") 45 | client.expect[Json](restock).unsafeRunSync() shouldBe json"""{ "eggs" : 10, "chocolate" : 20 }""" 46 | client.expect[Json](restock).unsafeRunSync() shouldBe json"""{ "eggs" : 20, "chocolate" : 40 }""" 47 | } 48 | "deliver purchase if there is enough inventory" in { 49 | val purchase = Request[IO](method = Method.POST, uri = Uri.unsafeFromString(s"$rootUrl/purchase")).withBody(json"""{ "order" : { "eggs": 5, "chocolate": 5 }}""") 50 | client.expect[Json](purchase).unsafeRunSync() shouldBe json"""{ "eggs" : 5, "chocolate" : 5 }""" 51 | } 52 | "not deliver purchase if there is not enough inventory" in { 53 | val purchase = Request[IO](method = Method.POST, uri = Uri.unsafeFromString(s"$rootUrl/purchase")).withBody(json"""{ "order" : { "eggs": 5, "chocolate": 45 }}""") 54 | client.expect[Json](purchase).unsafeRunSync() shouldBe json"""{ "eggs" : 0, "chocolate" : 0 }""" 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Chapter14/http4s-doobie/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | server { 2 | host = "0.0.0.0" 3 | port = 8080 4 | } 5 | 6 | database { 7 | driver = "org.h2.Driver" 8 | url = "jdbc:h2:mem:ch14;DB_CLOSE_DELAY=-1" 9 | user = "sa" 10 | password = "" 11 | } 12 | -------------------------------------------------------------------------------- /Chapter14/http4s-doobie/src/main/resources/db_migrations/V1__inventory_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE article ( 2 | name VARCHAR PRIMARY KEY, 3 | count INTEGER NOT NULL CHECK (count >= 0) 4 | ); 5 | -------------------------------------------------------------------------------- /Chapter14/http4s-doobie/src/main/scala/ch14/DB.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import cats.effect.IO 4 | import doobie.hikari.HikariTransactor 5 | import org.flywaydb.core.Flyway 6 | 7 | object DB { 8 | def transactor(config: DBConfig): IO[HikariTransactor[IO]] = { 9 | HikariTransactor.newHikariTransactor[IO](config.driver, 10 | config.url, 11 | config.user, 12 | config.password) 13 | } 14 | 15 | def initialize(transactor: HikariTransactor[IO]): IO[Unit] = { 16 | transactor.configure { dataSource => 17 | IO { 18 | val flyWay = new Flyway() 19 | flyWay.setLocations("classpath:db_migrations") 20 | flyWay.setDataSource(dataSource) 21 | flyWay.migrate() 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter14/http4s-doobie/src/main/scala/ch14/Model.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | object Model { 4 | type Inventory = Map[String, Int] 5 | 6 | abstract sealed class Operation(val inventory: Inventory) 7 | 8 | final case class Purchase(order: Inventory) 9 | extends Operation(order.mapValues(_ * -1)) 10 | 11 | final case class Restock(override val inventory: Inventory) 12 | extends Operation(inventory) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /Chapter14/http4s-doobie/src/main/scala/ch14/Repository.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import java.sql.SQLException 4 | 5 | import cats.effect.IO 6 | import ch14.Model.Inventory 7 | import fs2.Stream 8 | import doobie._ 9 | import doobie.implicits._ 10 | import doobie.util.transactor.Transactor 11 | import cats.implicits._ 12 | 13 | class Repository(transactor: Transactor[IO]) { 14 | 15 | def deleteArticle(name: String): IO[Boolean] = { 16 | sql"DELETE FROM article WHERE name = $name".update.run 17 | .transact(transactor) 18 | .map { affectedRows => 19 | affectedRows == 1 20 | } 21 | } 22 | 23 | def createArticle(name: String): IO[Boolean] = { 24 | sql"INSERT INTO article (name, count) VALUES ($name, 0)".update.run.attempt 25 | .transact(transactor) 26 | .map { 27 | case Right(affectedRows) => affectedRows == 1 28 | case Left(_) => false 29 | } 30 | } 31 | 32 | def updateStock(inventory: Inventory): Stream[IO, Either[Throwable, Unit]] = { 33 | val updates = inventory 34 | .map { 35 | case (name, count) => 36 | sql"UPDATE article set count = count + $count where name = $name".update.run 37 | } 38 | .reduce(_ *> _) 39 | Stream 40 | .eval(FC.setAutoCommit(false) *> updates *> FC.setAutoCommit(true)) 41 | .attempt 42 | .transact(transactor) 43 | } 44 | 45 | def getInventory: Stream[IO, Inventory] = 46 | queryToInventory(inventoryQuery) 47 | 48 | def getArticle(name: String): Stream[IO, Inventory] = 49 | queryToInventory( sql"SELECT name, count FROM article where name = $name") 50 | 51 | private val inventoryQuery: Fragment = sql"SELECT name, count FROM article" 52 | 53 | private def queryToInventory(query: Fragment) = 54 | query 55 | .query[(String, Int)] 56 | .stream 57 | .transact(transactor) 58 | .fold(Map.empty[String, Int])(_ + _) 59 | } 60 | -------------------------------------------------------------------------------- /Chapter14/http4s-doobie/src/main/scala/ch14/Server.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import cats.effect.IO 4 | import fs2.{Stream, StreamApp} 5 | import fs2.StreamApp.ExitCode 6 | import org.http4s.dsl.Http4sDsl 7 | import org.http4s.server.blaze.BlazeBuilder 8 | import scala.concurrent.ExecutionContext.Implicits.global 9 | 10 | object Server extends StreamApp[IO] with Http4sDsl[IO] { 11 | override def stream(args: List[String], 12 | requestShutdown: IO[Unit]): Stream[IO, ExitCode] = { 13 | val config = Config.load("application.conf") 14 | createServer(config).flatMap(_.serve) 15 | } 16 | 17 | def createServer(config: IO[Config]): Stream[IO, BlazeBuilder[IO]] = { 18 | for { 19 | config <- Stream.eval(config) 20 | transactor <- Stream.eval(DB.transactor(config.database)) 21 | _ <- Stream.eval(DB.initialize(transactor)) 22 | } yield BlazeBuilder[IO] 23 | .bindHttp(config.server.port, config.server.host) 24 | .mountService(new Service(new Repository(transactor)).service, "/") 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Chapter14/http4s-doobie/src/main/scala/ch14/Service.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import cats.effect.IO 4 | import ch14.Model.{Inventory, Purchase, Restock} 5 | import org.http4s.{HttpService, MediaType, Response} 6 | import org.http4s.dsl.Http4sDsl 7 | import org.http4s.circe._ 8 | import io.circe.generic.auto._ 9 | import io.circe.syntax._ 10 | import org.http4s.headers.`Content-Type` 11 | import fs2.Stream 12 | 13 | class Service(repo: Repository) extends Http4sDsl[IO] { 14 | 15 | val service = HttpService[IO] { 16 | case DELETE -> Root / "articles" / name => 17 | repo.deleteArticle(name).flatMap { if (_) NoContent() else NotFound() } 18 | 19 | case POST -> Root / "articles" / name => 20 | repo.createArticle(name).flatMap { if (_) NoContent() else Conflict() } 21 | 22 | case GET -> Root / "articles" / name => renderInventory(repo.getArticle(name)) 23 | 24 | case req @ POST -> Root / "purchase" => 25 | val changes: Stream[IO, Inventory] = for { 26 | purchase <- Stream.eval(req.decodeJson[Purchase]) 27 | before <- repo.getInventory 28 | _ <- repo.updateStock(purchase.inventory) 29 | after <- repo.getInventory 30 | } yield diff(purchase.order.keys, before, after) 31 | renderInventory(changes) 32 | 33 | case req @ POST -> Root / "restock" => 34 | val newState = for { 35 | purchase <- Stream.eval(req.decodeJson[Restock]) 36 | _ <- repo.updateStock(purchase.inventory) 37 | inventory <- repo.getInventory 38 | } yield inventory 39 | renderInventory(newState) 40 | 41 | case GET -> Root / "inventory" => 42 | getInventory 43 | } 44 | 45 | private def diff[A](keys: Iterable[A], 46 | before: Map[A, Int], 47 | after: Map[A, Int]): Map[A, Int] = 48 | keys.filter(before.contains).map { key => 49 | key -> (before(key) - after(key)) 50 | }.toMap 51 | 52 | private def getInventory: IO[Response[IO]] = 53 | renderInventory(repo.getInventory) 54 | 55 | private def renderInventory(inventory: Stream[IO, Inventory]) = 56 | Ok(inventory.map(_.asJson.noSpaces), 57 | `Content-Type`(MediaType.`application/json`)) 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Chapter14/http4s-doobie/src/main/scala/ch14/config.scala: -------------------------------------------------------------------------------- 1 | package ch14 2 | 3 | import cats.effect.IO 4 | import com.typesafe.config.ConfigFactory 5 | import pureconfig.error.ConfigReaderException 6 | 7 | case class ServerConfig(host: String, port: Int) 8 | 9 | case class DBConfig(driver: String, url: String, user: String, password: String) 10 | 11 | case class Config(server: ServerConfig, database: DBConfig) 12 | 13 | object Config { 14 | def load(fileName: String): IO[Config] = { 15 | IO { 16 | val config = ConfigFactory.load(fileName) 17 | pureconfig.loadConfig[Config](config) 18 | }.flatMap { 19 | case Left(e) => 20 | IO.raiseError[Config](new ConfigReaderException[Config](e)) 21 | case Right(config) => 22 | IO.pure(config) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter14/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.3 -------------------------------------------------------------------------------- /Chapter14/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.1.0") -------------------------------------------------------------------------------- /Chapter15/.sbtopts: -------------------------------------------------------------------------------- 1 | -J-Xms512M 2 | -J-Xmx4096M 3 | -J-Xss2M 4 | -J-XX:MaxMetaspaceSize=1024M 5 | -------------------------------------------------------------------------------- /Chapter15/baker-api/src/main/scala/ch15/BakerService.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import ch15.model._ 4 | import com.lightbend.lagom.scaladsl.api._ 5 | import Service._ 6 | 7 | import akka.NotUsed 8 | import akka.stream.scaladsl.Source 9 | 10 | trait BakerService extends Service { 11 | def bake: ServiceCall[Source[RawCookies, NotUsed], Source[ReadyCookies, NotUsed]] 12 | 13 | override def descriptor: Descriptor = named("BakerService").withCalls(call(bake)) 14 | } 15 | -------------------------------------------------------------------------------- /Chapter15/baker-impl/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | play.application.loader = ch15.BakerLoader 2 | -------------------------------------------------------------------------------- /Chapter15/baker-impl/src/main/scala/ch15/bakerLoader.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import com.lightbend.lagom.scaladsl.api.ServiceLocator 4 | import com.lightbend.lagom.scaladsl.api.ServiceLocator.NoServiceLocator 5 | import com.lightbend.lagom.scaladsl.devmode.LagomDevModeComponents 6 | import com.lightbend.lagom.scaladsl.server._ 7 | import com.softwaremill.macwire._ 8 | import play.api.libs.ws.ahc.AhcWSComponents 9 | 10 | 11 | abstract class BakerApplication(context: LagomApplicationContext) 12 | extends LagomApplication(context) with AhcWSComponents { 13 | override lazy val lagomServer: LagomServer = serverFor[BakerService](wire[BakerServiceImpl]) 14 | } 15 | 16 | class BakerLoader extends LagomApplicationLoader { 17 | 18 | override def load(context: LagomApplicationContext): LagomApplication = 19 | new BakerApplication(context) { 20 | override def serviceLocator: ServiceLocator = NoServiceLocator 21 | } 22 | 23 | override def loadDevMode(context: LagomApplicationContext): LagomApplication = 24 | new BakerApplication(context) with LagomDevModeComponents 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Chapter15/baker-impl/src/main/scala/ch15/bakerServiceImpl.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import akka.NotUsed 4 | import akka.stream.{Attributes, DelayOverflowStrategy} 5 | import akka.stream.scaladsl.{BidiFlow, Flow, Source} 6 | import ch15.model._ 7 | import com.lightbend.lagom.scaladsl.api._ 8 | 9 | import scala.concurrent.duration._ 10 | import scala.concurrent.Future 11 | 12 | import play.api.Logger 13 | 14 | class BakerServiceImpl extends BakerService { 15 | 16 | private val logger = Logger("Baker") 17 | 18 | override def bake: ServiceCall[Source[RawCookies, NotUsed], Source[ReadyCookies, NotUsed]] = ServiceCall { dough => 19 | logger.info(s"Baking: $dough") 20 | Future.successful(dough.via(bakerFlow)) 21 | } 22 | 23 | private val bakerFlow: Flow[RawCookies, ReadyCookies, NotUsed] = 24 | Baker.bakeFlow.join(Oven.bakeFlow) 25 | } 26 | 27 | object Baker { 28 | private val logger = Logger("BakerFlow") 29 | 30 | def bakeFlow: BidiFlow[RawCookies, RawCookies, ReadyCookies, ReadyCookies, NotUsed] = BidiFlow.fromFlows(inFlow, outFlow) 31 | 32 | private val inFlow = Flow[RawCookies] 33 | .flatMapConcat(extractFromBox) 34 | .grouped(Oven.ovenSize) 35 | .map(_.reduce(_ + _)) 36 | 37 | private def outFlow = Flow[ReadyCookies].map { c => 38 | logger.info(s"Sending to manager: $c") 39 | c 40 | } 41 | 42 | private def extractFromBox(c: RawCookies) = { 43 | logger.info(s"Extracting: $c") 44 | Source(List.fill(c.count)(RawCookies(1))) 45 | } 46 | } 47 | 48 | object Oven { 49 | private val logger = Logger("Oven") 50 | 51 | val ovenSize = 12 52 | private val bakingTime = 2.seconds 53 | 54 | def bakeFlow: Flow[RawCookies, ReadyCookies, NotUsed] = 55 | Flow[RawCookies] 56 | .map(bake) 57 | .delay(bakingTime, DelayOverflowStrategy.backpressure) 58 | .addAttributes(Attributes.inputBuffer(1, 1)) 59 | 60 | private def bake(c: RawCookies): ReadyCookies = { 61 | logger.info(s"Baked: $c") 62 | assert(c.count == ovenSize) 63 | ReadyCookies(c.count) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Chapter15/baker-impl/src/test/scala/ch15/BakerServiceSpec.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import akka.NotUsed 4 | import akka.stream.Materializer 5 | import akka.stream.scaladsl.Source 6 | import akka.stream.testkit.scaladsl.TestSink 7 | import ch15.model.{RawCookies, ReadyCookies} 8 | import com.lightbend.lagom.scaladsl.server.LocalServiceLocator 9 | import com.lightbend.lagom.scaladsl.testkit.ServiceTest 10 | import org.scalatest.{AsyncWordSpec, Matchers} 11 | 12 | class BakerServiceSpec extends AsyncWordSpec with Matchers { 13 | 14 | "The BakerService" should { 15 | "bake cookies" in ServiceTest.withServer(ServiceTest.defaultSetup) { ctx => 16 | new BakerApplication(ctx) with LocalServiceLocator 17 | } { server => 18 | implicit val as: Materializer = server.materializer 19 | val input: Source[RawCookies, NotUsed] = 20 | Source(List(RawCookies(10), RawCookies(10), RawCookies(10))) 21 | .concat(Source.maybe) 22 | 23 | val client = server.serviceClient.implement[BakerService] 24 | 25 | client.bake.invoke(input).map { output => 26 | val probe = output.runWith(TestSink.probe(server.actorSystem)) 27 | probe.request(10) 28 | probe.expectNext(ReadyCookies(12)) 29 | probe.expectNext(ReadyCookies(12)) 30 | // because the oven is not full for the 6 other 31 | probe.cancel 32 | succeed 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Chapter15/boy-api/src/main/scala/ch15/BoyService.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import ch15.model._ 4 | import com.lightbend.lagom.scaladsl.api._ 5 | import Service._ 6 | 7 | trait BoyService extends Service { 8 | def shop: ServiceCall[ShoppingList, Groceries] 9 | 10 | override def descriptor: Descriptor = { 11 | named("BoyService").withCalls(namedCall("go-shopping", shop)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Chapter15/boy-api/src/main/scala/ch15/ShopService.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import ch15.model._ 4 | import com.lightbend.lagom.scaladsl.api.Service._ 5 | import com.lightbend.lagom.scaladsl.api._ 6 | import play.api.libs.json.{Format, Json} 7 | import ShopService._ 8 | import com.lightbend.lagom.scaladsl.api.transport.Method 9 | 10 | trait ShopService extends Service { 11 | def order: ServiceCall[Order, Purchase] 12 | 13 | override def descriptor: Descriptor = { 14 | named("GroceryShop").withCalls(restCall(Method.POST, "/purchase", order)) 15 | } 16 | } 17 | 18 | object ShopService { 19 | final case class Order(order: ShoppingList) 20 | final case class Purchase(order: Groceries) 21 | implicit val purchase: Format[Purchase] = Json.format 22 | implicit val order: Format[Order] = Json.format 23 | } 24 | -------------------------------------------------------------------------------- /Chapter15/boy-impl/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | play.application.loader = ch15.BoyLoader 2 | -------------------------------------------------------------------------------- /Chapter15/boy-impl/src/main/scala/ch15/BoyServiceImpl.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import ch15.ShopService._ 4 | import ch15.model._ 5 | import com.lightbend.lagom.scaladsl.api._ 6 | import play.api.Logger 7 | 8 | import scala.concurrent.{ExecutionContext, Future} 9 | 10 | class BoyServiceImpl(shopService: ShopService)(implicit ec: ExecutionContext) 11 | extends BoyService { 12 | private val logger = Logger("Boy") 13 | override def shop: ServiceCall[ShoppingList, Groceries] = 14 | ServiceCall(callExtApi) 15 | 16 | private val callExtApi: ShoppingList => Future[Groceries] = list => 17 | shopService.order.invoke(Order(list)).map(_.order).recover { 18 | case th: Throwable => 19 | logger.error(th.getMessage, th) 20 | Groceries(0, 0, 0, 0) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Chapter15/boy-impl/src/main/scala/ch15/boyLoader.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import com.lightbend.lagom.scaladsl.api.ServiceLocator 4 | import com.lightbend.lagom.scaladsl.api.ServiceLocator.NoServiceLocator 5 | import com.lightbend.lagom.scaladsl.devmode.LagomDevModeComponents 6 | import com.lightbend.lagom.scaladsl.server._ 7 | import com.softwaremill.macwire._ 8 | import play.api.libs.ws.ahc.AhcWSComponents 9 | 10 | abstract class BoyApplication(context: LagomApplicationContext) 11 | extends LagomApplication(context) with AhcWSComponents { 12 | lazy val shopService: ShopService = serviceClient.implement[ShopService] 13 | override lazy val lagomServer: LagomServer = serverFor[BoyService](wire[BoyServiceImpl]) 14 | } 15 | 16 | class BoyLoader extends LagomApplicationLoader { 17 | 18 | override def load(context: LagomApplicationContext) = 19 | new BoyApplication(context) { 20 | override def serviceLocator: ServiceLocator = NoServiceLocator 21 | } 22 | 23 | override def loadDevMode(context: LagomApplicationContext) = 24 | new BoyApplication(context) with LagomDevModeComponents 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Chapter15/build.sbt: -------------------------------------------------------------------------------- 1 | organization in ThisBuild := "packt" 2 | version in ThisBuild := "1.0-SNAPSHOT" 3 | 4 | scalaVersion in ThisBuild := "2.13.0" 5 | 6 | val macwire = "com.softwaremill.macwire" %% "macros" % "2.3.3" % Provided 7 | val scalaTest = "org.scalatest" %% "scalatest" % "3.0.8" % Test 8 | 9 | val defaultDependencies = Seq(lagomScaladslTestKit, macwire, scalaTest) 10 | 11 | lazy val bakery = (project in file(".")) 12 | .aggregate( 13 | `shared-model`, 14 | `boy-api`, 15 | `boy-impl`, 16 | `chef-api`, 17 | `chef-impl`, 18 | `cook-api`, 19 | `cook-impl`, 20 | `baker-api`, 21 | `baker-impl`, 22 | `manager-api`, 23 | `manager-impl` 24 | ) 25 | 26 | lazy val `shared-model` = (project in file("shared-model")) 27 | .settings(libraryDependencies += lagomScaladslApi) 28 | 29 | lazy val `boy-api` = (project in file("boy-api")) 30 | .settings(libraryDependencies += lagomScaladslApi) 31 | .dependsOn(`shared-model`) 32 | 33 | lazy val `chef-api` = (project in file("chef-api")) 34 | .settings(libraryDependencies += lagomScaladslApi) 35 | .dependsOn(`shared-model`) 36 | 37 | lazy val `cook-api` = (project in file("cook-api")) 38 | .settings(libraryDependencies += lagomScaladslApi) 39 | .dependsOn(`shared-model`) 40 | 41 | lazy val `baker-api` = (project in file("baker-api")) 42 | .settings(libraryDependencies += lagomScaladslApi) 43 | .dependsOn(`shared-model`) 44 | 45 | lazy val `manager-api` = (project in file("manager-api")) 46 | .settings(libraryDependencies += lagomScaladslApi) 47 | .dependsOn(`shared-model`) 48 | 49 | lazy val `boy-impl` = (project in file("boy-impl")) 50 | .enablePlugins(LagomScala) 51 | .settings(libraryDependencies ++= defaultDependencies) 52 | .dependsOn(`boy-api`) 53 | 54 | lazy val `chef-impl` = (project in file("chef-impl")) 55 | .enablePlugins(LagomScala) 56 | .settings( 57 | libraryDependencies ++= Seq( 58 | lagomScaladslPersistenceCassandra, 59 | lagomScaladslKafkaBroker, 60 | lagomScaladslTestKit, 61 | lagomScaladslPubSub, 62 | macwire 63 | ) 64 | ) 65 | .settings(lagomForkedTestSettings: _*) 66 | .dependsOn(`chef-api`) 67 | 68 | lazy val `cook-impl` = (project in file("cook-impl")) 69 | .enablePlugins(LagomScala) 70 | .settings(libraryDependencies ++= defaultDependencies) 71 | .dependsOn(`cook-api`) 72 | 73 | lazy val `baker-impl` = (project in file("baker-impl")) 74 | .enablePlugins(LagomScala) 75 | .settings(libraryDependencies ++= defaultDependencies) 76 | .dependsOn(`baker-api`) 77 | 78 | lazy val `manager-impl` = (project in file("manager-impl")) 79 | .enablePlugins(LagomScala) 80 | .settings(libraryDependencies ++= defaultDependencies) 81 | .settings(libraryDependencies += lagomScaladslKafkaBroker) 82 | .dependsOn(`manager-api`, `boy-api`, `chef-api`, `cook-api`, `baker-api`) 83 | 84 | lagomUnmanagedServices in ThisBuild := Map( 85 | "GroceryShop" -> "http://localhost:8080" 86 | ) 87 | -------------------------------------------------------------------------------- /Chapter15/chef-api/src/main/scala/ch15/ChefService.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import ch15.model._ 4 | import com.lightbend.lagom.scaladsl.api._ 5 | import Service._ 6 | import akka.Done 7 | import com.lightbend.lagom.scaladsl.api.broker.Topic 8 | 9 | trait ChefService extends Service { 10 | def mix: ServiceCall[Groceries, Done] 11 | 12 | def resultsTopic: Topic[Dough] 13 | 14 | override def descriptor: Descriptor = { 15 | named("ChefService") 16 | .withCalls(call(mix)) 17 | .withTopics( 18 | topic(ChefService.ResultsTopic, resultsTopic) 19 | ) 20 | .withAutoAcl(true) 21 | } 22 | } 23 | object ChefService { 24 | val ResultsTopic = "MixedResults" 25 | } 26 | -------------------------------------------------------------------------------- /Chapter15/chef-impl/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | play.application.loader = ch15.ChefLoader 2 | 3 | user.cassandra.keyspace = chefprogress 4 | 5 | cassandra-journal.keyspace = ${user.cassandra.keyspace} 6 | cassandra-snapshot-store.keyspace = ${user.cassandra.keyspace} 7 | lagom.persistence.read-side.cassandra.keyspace = ${user.cassandra.keyspace} 8 | -------------------------------------------------------------------------------- /Chapter15/chef-impl/src/main/scala/ch15/ChefPersistentEntity.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import java.util.UUID 4 | 5 | import akka.Done 6 | import akka.actor.ActorSystem 7 | import ch15.model.{Dough, Groceries} 8 | import com.lightbend.lagom.scaladsl.persistence.{PersistentEntity, PersistentEntityRegistry} 9 | 10 | import scala.concurrent.duration._ 11 | import scala.concurrent.ExecutionContext 12 | 13 | final class ChefPersistentEntity(persistentEntities: PersistentEntityRegistry, 14 | as: ActorSystem) 15 | extends PersistentEntity { 16 | 17 | override type Command = ChefCommand 18 | override type Event = ChefEvent 19 | override type State = ChefState 20 | 21 | override def initialState: ChefState = MixingState(Nil) 22 | 23 | private val mixingTime = 1.second 24 | 25 | private def dough(g: Groceries) = { 26 | import g._ 27 | Dough(eggs * 50 + flour + sugar + chocolate) 28 | } 29 | 30 | implicit val ec: ExecutionContext = as.dispatcher 31 | lazy val thisEntity = 32 | persistentEntities.refFor[ChefPersistentEntity](this.entityId) 33 | 34 | override def behavior: Behavior = 35 | Actions() 36 | .onCommand[MixCommand, Done] { 37 | case (MixCommand(groceries), ctx, _) if groceries.eggs <= 0 => 38 | ctx.invalidCommand(s"Need at least one egg but got: $groceries") 39 | ctx.done 40 | 41 | case (MixCommand(groceries), ctx, _) => 42 | val id = UUID.randomUUID() 43 | ctx.thenPersist(Mixing(id, groceries)) { evt => 44 | as.scheduler.scheduleOnce(mixingTime)( 45 | thisEntity.ask(DoneCommand(id))) 46 | ctx.reply(Done) 47 | } 48 | } 49 | .onCommand[DoneCommand, Done] { 50 | case (DoneCommand(id), ctx, state) => 51 | state.batches 52 | .find(_.id == id) 53 | .map { g => 54 | ctx.thenPersist(MixingDone(id, dough(g.groceries))) { _ => 55 | ctx.reply(Done) 56 | } 57 | } 58 | .getOrElse(ctx.done) 59 | } 60 | .onEvent { 61 | case (m: Mixing, state) => 62 | MixingState(state.batches :+ m) 63 | 64 | case (MixingDone(id, _), state) => 65 | MixingState(state.batches.filterNot(_.id == id)) 66 | } 67 | 68 | // TODO restart mixing after recovery 69 | } 70 | -------------------------------------------------------------------------------- /Chapter15/chef-impl/src/main/scala/ch15/ChefServiceImpl.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import akka.Done 4 | import akka.actor.ActorSystem 5 | import ch15.model._ 6 | import com.lightbend.lagom.scaladsl.api._ 7 | import com.lightbend.lagom.scaladsl.api.broker.Topic 8 | import com.lightbend.lagom.scaladsl.broker.TopicProducer 9 | import com.lightbend.lagom.scaladsl.persistence.{EventStreamElement, PersistentEntityRegistry} 10 | import com.softwaremill.macwire.wire 11 | 12 | class ChefServiceImpl(persistentEntities: PersistentEntityRegistry, 13 | as: ActorSystem) extends ChefService { 14 | 15 | private lazy val entity = wire[ChefPersistentEntity] 16 | persistentEntities.register(entity) 17 | 18 | override def mix: ServiceCall[Groceries, Done] = ServiceCall { groceries => 19 | val ref = persistentEntities.refFor[ChefPersistentEntity]("Chef") 20 | ref.ask(MixCommand(groceries)) 21 | } 22 | 23 | override def resultsTopic: Topic[Dough] = 24 | TopicProducer.singleStreamWithOffset { fromOffset => 25 | persistentEntities 26 | .eventStream(ChefModel.EventTag, fromOffset) 27 | .map { ev => (convertEvent(ev), ev.offset) } 28 | } 29 | 30 | private def convertEvent(chefEvent: EventStreamElement[ChefEvent]): Dough = { 31 | chefEvent.event match { 32 | case MixingDone(_, dough) => dough 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Chapter15/chef-impl/src/main/scala/ch15/chefLoader.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import com.lightbend.lagom.scaladsl.api.ServiceLocator 4 | import com.lightbend.lagom.scaladsl.api.ServiceLocator.NoServiceLocator 5 | import com.lightbend.lagom.scaladsl.broker.kafka.LagomKafkaComponents 6 | import com.lightbend.lagom.scaladsl.devmode.LagomDevModeComponents 7 | import com.lightbend.lagom.scaladsl.persistence.cassandra.CassandraPersistenceComponents 8 | import com.lightbend.lagom.scaladsl.playjson.JsonSerializerRegistry 9 | import com.lightbend.lagom.scaladsl.server._ 10 | import com.softwaremill.macwire._ 11 | import play.api.libs.ws.ahc.AhcWSComponents 12 | 13 | 14 | abstract class ChefApplication(context: LagomApplicationContext) 15 | extends LagomApplication(context) with CassandraPersistenceComponents with LagomKafkaComponents { 16 | override lazy val lagomServer: LagomServer = serverFor[ChefService](wire[ChefServiceImpl]) 17 | override lazy val jsonSerializerRegistry = new JsonSerializerRegistry { 18 | override def serializers = ChefModel.serializers 19 | } 20 | } 21 | 22 | class ChefLoader extends LagomApplicationLoader { 23 | 24 | override def load(context: LagomApplicationContext): LagomApplication = 25 | new ChefApplication(context) with AhcWSComponents { 26 | override def serviceLocator: ServiceLocator = NoServiceLocator 27 | } 28 | 29 | override def loadDevMode(context: LagomApplicationContext): LagomApplication = 30 | new ChefApplication(context) with AhcWSComponents with LagomDevModeComponents 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Chapter15/chef-impl/src/main/scala/ch15/chefModel.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import java.util.UUID 4 | 5 | import akka.Done 6 | import ch15.model.{Dough, Groceries} 7 | import com.lightbend.lagom.scaladsl.persistence.{AggregateEvent, AggregateEventTag} 8 | import com.lightbend.lagom.scaladsl.persistence.PersistentEntity.ReplyType 9 | import com.lightbend.lagom.scaladsl.playjson.JsonSerializer 10 | 11 | 12 | sealed trait ChefCommand 13 | final case class MixCommand(groceries: Groceries) extends ChefCommand with ReplyType[Done] 14 | final case class DoneCommand(id: UUID) extends ChefCommand with ReplyType[Done] 15 | 16 | sealed trait ChefEvent 17 | final case class Mixing(id: UUID, groceries: Groceries) extends ChefEvent 18 | final case class MixingDone(id: UUID, dough: Dough) extends ChefEvent with AggregateEvent[MixingDone] { 19 | override def aggregateTag: AggregateEventTag[MixingDone] = ChefModel.EventTag 20 | } 21 | 22 | sealed trait ChefState { 23 | def batches: List[Mixing] 24 | } 25 | final case class MixingState(batches: List[Mixing]) 26 | extends ChefState 27 | 28 | object ChefModel { 29 | val EventTag: AggregateEventTag[MixingDone] = AggregateEventTag[MixingDone]("MixingDone") 30 | import play.api.libs.json._ 31 | implicit val mixingFormat: OFormat[Mixing] = Json.format[Mixing] 32 | val serializers = List( 33 | JsonSerializer(mixingFormat), 34 | JsonSerializer(Json.format[MixingDone]), 35 | JsonSerializer(Json.format[MixingState])) 36 | } 37 | -------------------------------------------------------------------------------- /Chapter15/cook-api/src/main/scala/ch15/CookService.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import ch15.model._ 4 | import com.lightbend.lagom.scaladsl.api._ 5 | 6 | trait CookService extends Service { 7 | def cook: ServiceCall[Dough, RawCookies] 8 | 9 | override def descriptor: Descriptor = { 10 | import Service._ 11 | named("CookService").withCalls(call(cook)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Chapter15/cook-impl/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | play.application.loader = ch15.CookLoader 2 | -------------------------------------------------------------------------------- /Chapter15/cook-impl/src/main/scala/ch15/CookServiceImpl.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import ch15.model._ 4 | import com.lightbend.lagom.scaladsl.api._ 5 | 6 | import scala.concurrent.Future 7 | 8 | class CookServiceImpl extends CookService { 9 | override def cook: ServiceCall[Dough, RawCookies] = ServiceCall { dough => 10 | Future.successful(RawCookies(makeCookies(dough.weight))) 11 | } 12 | private val cookieWeight = 60 13 | private def makeCookies(weight: Int): Int = weight / cookieWeight 14 | } 15 | -------------------------------------------------------------------------------- /Chapter15/cook-impl/src/main/scala/ch15/cookLoader.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import com.lightbend.lagom.scaladsl.api.ServiceLocator 4 | import com.lightbend.lagom.scaladsl.api.ServiceLocator.NoServiceLocator 5 | import com.lightbend.lagom.scaladsl.devmode.LagomDevModeComponents 6 | import com.lightbend.lagom.scaladsl.server._ 7 | import com.softwaremill.macwire._ 8 | import play.api.libs.ws.ahc.AhcWSComponents 9 | 10 | 11 | abstract class CookApplication(context: LagomApplicationContext) 12 | extends LagomApplication(context) { 13 | override lazy val lagomServer: LagomServer = serverFor[CookService](wire[CookServiceImpl]) 14 | } 15 | 16 | class CookLoader extends LagomApplicationLoader { 17 | 18 | override def load(context: LagomApplicationContext): LagomApplication = 19 | new CookApplication(context) with AhcWSComponents { 20 | override def serviceLocator: ServiceLocator = NoServiceLocator 21 | } 22 | 23 | override def loadDevMode(context: LagomApplicationContext): LagomApplication = 24 | new CookApplication(context) with AhcWSComponents with LagomDevModeComponents 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Chapter15/cook-impl/src/test/scala/ch15/CookServiceSpec.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import ch15.model.Dough 4 | import com.lightbend.lagom.scaladsl.server.LocalServiceLocator 5 | import com.lightbend.lagom.scaladsl.testkit.ServiceTest 6 | import org.scalatest.{AsyncWordSpec, Matchers} 7 | import play.api.libs.ws.ahc.AhcWSComponents 8 | 9 | class CookServiceSpec extends AsyncWordSpec with Matchers { 10 | 11 | "The CookService" should { 12 | "make cookies from Dough" in ServiceTest.withServer(ServiceTest.defaultSetup) { ctx => 13 | new CookApplication(ctx) with LocalServiceLocator with AhcWSComponents 14 | } { server => 15 | val client = server.serviceClient.implement[CookService] 16 | 17 | client.cook.invoke(Dough(200)).map { cookies => 18 | cookies.count should ===(3) 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Chapter15/manager-api/src/main/scala/ch15/ManagerService.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import akka.{Done, NotUsed} 4 | import com.lightbend.lagom.scaladsl.api._ 5 | import com.lightbend.lagom.scaladsl.api.transport.Method 6 | 7 | trait ManagerService extends Service { 8 | def bake(count: Int): ServiceCall[NotUsed, Done] 9 | def sell(count: Int): ServiceCall[NotUsed, Int] 10 | def report: ServiceCall[NotUsed, Int] 11 | 12 | override def descriptor: Descriptor = { 13 | import Service._ 14 | named("Bakery").withCalls( 15 | restCall(Method.POST, "/bake/:count", bake _), 16 | restCall(Method.POST, "/sell?count", sell _), 17 | pathCall("/report", report) 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter15/manager-impl/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | play.application.loader = ch15.ManagerLoader 2 | -------------------------------------------------------------------------------- /Chapter15/manager-impl/src/main/scala/ch15/ManagerServiceImpl.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import java.util.concurrent.atomic.AtomicInteger 4 | 5 | import akka.actor.ActorSystem 6 | import akka.stream.ActorMaterializer 7 | import akka.stream.scaladsl.{Flow, Sink, Source} 8 | import akka.{Done, NotUsed} 9 | import ch15.model.{dough, _} 10 | import com.lightbend.lagom.scaladsl.api._ 11 | import com.lightbend.lagom.scaladsl.persistence.{AggregateEvent, AggregateEventTag} 12 | import com.lightbend.lagom.scaladsl.persistence.PersistentEntity.ReplyType 13 | import play.api.Logger 14 | 15 | import scala.concurrent.{ExecutionContext, Future, Promise} 16 | 17 | class ManagerServiceImpl(boyService: BoyService, 18 | chefService: ChefService, 19 | cookService: CookService, 20 | bakerService: BakerService, 21 | as: ActorSystem) 22 | extends ManagerService { 23 | 24 | private val count: AtomicInteger = new AtomicInteger(0) 25 | 26 | private val logger = Logger("Manager") 27 | 28 | override def bake(count: Int): ServiceCall[NotUsed, Done] = ServiceCall { _ => 29 | val sl = shoppingList(count) 30 | logger.info(s"Shopping list: $sl") 31 | for { 32 | groceries <- boyService.shop.invoke(sl) 33 | done <- chefService.mix.invoke(groceries) 34 | } yield { 35 | logger.info(s"Sent $groceries to Chef") 36 | done 37 | } 38 | } 39 | 40 | override def sell(cnt: Int): ServiceCall[NotUsed, Int] = ServiceCall { _ => 41 | if (cnt > count.get()) { 42 | Future.failed( 43 | new IllegalStateException(s"Only $count cookies are available")) 44 | } else { 45 | count.addAndGet(-1 * cnt) 46 | Future.successful(cnt) 47 | } 48 | } 49 | 50 | override def report: ServiceCall[NotUsed, Int] = ServiceCall { _ => 51 | Future.successful(count.get()) 52 | } 53 | 54 | private def shoppingList(count: Int): ShoppingList = 55 | ShoppingList(count, count * 30, count * 10, count * 5) 56 | 57 | private implicit lazy val ec: ExecutionContext = as.dispatcher 58 | private implicit lazy val am: ActorMaterializer = ActorMaterializer()(as) 59 | 60 | val sub: Future[Done] = 61 | chefService.resultsTopic.subscribe.atLeastOnce(chefFlow) 62 | 63 | private def update(cookies: ReadyCookies) = { 64 | logger.info(s"Got baked cookies, now there are $cookies available") 65 | count.addAndGet(cookies.count) 66 | } 67 | private lazy val chefFlow: Flow[Dough, Done, NotUsed] = Flow[Dough] 68 | .map { dough: Dough => 69 | val fut = cookService.cook.invoke(dough) 70 | val src = Source.fromFuture(fut) 71 | val ready: Future[Source[ReadyCookies, NotUsed]] = 72 | bakerService.bake.invoke(src) 73 | //val ready: Future[Source[ReadyCookies, NotUsed]] = Future.successful(src.map(r => ReadyCookies(12)).take(2)) 74 | Source.fromFutureSource(ready) 75 | } 76 | .flatMapConcat(identity) 77 | .map(update) 78 | .map(_ => Done) 79 | 80 | lazy val dummyflow: Flow[Dough, Done, NotUsed] = Flow[Dough] 81 | .map { dough => 82 | logger.info(s"Dough: $dough") 83 | Source.fromFuture(cookService.cook.invoke(dough)).map { r => 84 | logger.info(s"Done $r"); Done 85 | } 86 | } 87 | .flatMapConcat(identity) 88 | 89 | } 90 | 91 | trait ManagerCommand 92 | final case class AddCookies(count: Int) extends ManagerCommand with ReplyType[Int] 93 | final case class RemoveCookies(count: Int) extends ManagerCommand with ReplyType[Int] 94 | 95 | trait ManagerEvent 96 | final case class NumberOfCookiesChanged(count: Int) extends ManagerEvent with AggregateEvent[NumberOfCookiesChanged] { 97 | override def aggregateTag: AggregateEventTag[NumberOfCookiesChanged] = AggregateEventTag[NumberOfCookiesChanged]("NumberOfCookiesChanged") 98 | } 99 | sealed trait ManagerState { 100 | def cookies: Int 101 | } 102 | final case class MixingState(cookies: Int) extends ManagerState 103 | -------------------------------------------------------------------------------- /Chapter15/manager-impl/src/main/scala/ch15/managerLoader.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | import com.lightbend.lagom.scaladsl.api.ServiceLocator 4 | import com.lightbend.lagom.scaladsl.api.ServiceLocator.NoServiceLocator 5 | import com.lightbend.lagom.scaladsl.broker.kafka.LagomKafkaClientComponents 6 | import com.lightbend.lagom.scaladsl.devmode.LagomDevModeComponents 7 | import com.lightbend.lagom.scaladsl.server._ 8 | import com.softwaremill.macwire._ 9 | import play.api.libs.ws.ahc.AhcWSComponents 10 | 11 | 12 | abstract class ManagerApplication(context: LagomApplicationContext) 13 | extends LagomApplication(context) with LagomKafkaClientComponents { 14 | lazy val boyService: BoyService = serviceClient.implement[BoyService] 15 | lazy val chefService: ChefService = serviceClient.implement[ChefService] 16 | lazy val cookService: CookService = serviceClient.implement[CookService] 17 | lazy val bakerService: BakerService = serviceClient.implement[BakerService] 18 | override lazy val lagomServer: LagomServer = 19 | serverFor[ManagerService](wire[ManagerServiceImpl]) 20 | } 21 | 22 | class ManagerLoader extends LagomApplicationLoader { 23 | 24 | override def load(context: LagomApplicationContext): LagomApplication = 25 | new ManagerApplication(context) with AhcWSComponents { 26 | override def serviceLocator: ServiceLocator = NoServiceLocator 27 | } 28 | 29 | override def loadDevMode(context: LagomApplicationContext): LagomApplication = 30 | new ManagerApplication(context) with AhcWSComponents 31 | with LagomDevModeComponents 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Chapter15/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /Chapter15/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.lightbend.lagom" % "lagom-sbt-plugin" % "1.6.0-M4") 2 | -------------------------------------------------------------------------------- /Chapter15/shared-model/src/main/scala/ch15/model.scala: -------------------------------------------------------------------------------- 1 | package ch15 2 | 3 | object model { 4 | final case class ShoppingList(eggs: Int, flour: Int, sugar: Int, chocolate: Int) 5 | final case class Groceries(eggs: Int, flour: Int, sugar: Int, chocolate: Int) 6 | final case class Dough(weight: Int) 7 | final case class RawCookies(count: Int) { 8 | def +(c: RawCookies): RawCookies = RawCookies(count + c.count) 9 | } 10 | final case class ReadyCookies(count: Int) 11 | 12 | import play.api.libs.json._ 13 | 14 | implicit val dough: Format[Dough] = Json.format 15 | implicit val rawCookies: Format[RawCookies] = Json.format 16 | implicit val readyCookies: Format[ReadyCookies] = Json.format 17 | implicit val groceries: Format[Groceries] = Json.format 18 | implicit val shoppingList: Format[ShoppingList] = Json.format 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | organization := "packt" 2 | version := "1.0-SNAPSHOT" 3 | scalaVersion := "2.13.0-M5" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked") 6 | --------------------------------------------------------------------------------