├── .gitignore ├── .gitmodules ├── README.md ├── lectures ├── 01-intro.html ├── 01-intro.md ├── 02-fp-with-scala.html ├── 02-fp-with-scala.md ├── 03-oop-in-a-functional-language.html ├── 03-oop-in-a-functional-language.md ├── 04-key-fp-approaches.html ├── 04-key-fp-approaches.md ├── 05-folds-collections.ipynb ├── 06-adts-are-the-root-of-all-evil.ipynb ├── 07-effects-and-functional-error-handling.ipynb ├── 08-concurrency.html ├── 08-concurrency.md ├── 09-type-classes.html ├── 09-type-classes.md ├── 10-monads-and-applicatives.html ├── 10-monads-and-applicatives.md ├── 11-cats-and-cats-effect.html ├── 11-cats-and-cats-effect.md ├── 12-building-a-scala-app.html ├── 12-building-a-scala-app.md ├── build.sh ├── examples │ ├── 02-fp-with-scala │ │ ├── examples │ │ │ ├── build.sbt │ │ │ ├── project │ │ │ │ ├── build.properties │ │ │ │ └── plugins.sbt │ │ │ └── src │ │ │ │ └── main │ │ │ │ └── scala │ │ │ │ └── scalafmi │ │ │ │ ├── Balance.scala │ │ │ │ ├── Examples.scala │ │ │ │ ├── Expressions.scala │ │ │ │ ├── FunctionalVsImperative.scala │ │ │ │ └── Substrings.scala │ │ ├── hello-scala-3 │ │ │ ├── build.sbt │ │ │ ├── project │ │ │ │ ├── build.properties │ │ │ │ └── plugins.sbt │ │ │ └── src │ │ │ │ ├── main │ │ │ │ └── scala │ │ │ │ │ └── HelloWorld.scala │ │ │ │ └── test │ │ │ │ └── scala │ │ │ │ └── ExampleSpec.scala │ │ └── hello-world │ │ │ ├── build.sbt │ │ │ ├── project │ │ │ └── build.properties │ │ │ └── src │ │ │ ├── main │ │ │ └── scala │ │ │ │ └── HelloWorld.scala │ │ │ └── test │ │ │ └── scala │ │ │ └── ExampleSpec.scala │ ├── 03-oop-in-a-functional-language │ │ ├── build.sbt │ │ ├── project │ │ │ ├── build.properties │ │ │ └── plugins.sbt │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ ├── expressionproblem │ │ │ ├── fp │ │ │ │ └── Shape.scala │ │ │ └── oop │ │ │ │ └── Shape.scala │ │ │ ├── mathematical │ │ │ └── Rational.scala │ │ │ └── solutions │ │ │ └── mathematical │ │ │ ├── v1 │ │ │ └── Rational.scala │ │ │ └── v2 │ │ │ └── Rational.scala │ ├── 04-key-fp-approaches │ │ ├── build.sbt │ │ ├── project │ │ │ ├── build.properties │ │ │ └── plugins.sbt │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ ├── answers │ │ │ ├── HigherOrderFunctions.scala │ │ │ ├── Recursion.scala │ │ │ └── multiparameters │ │ │ │ ├── GroupingOfParameters.scala │ │ │ │ ├── LanguageConstructs.scala │ │ │ │ └── TypeInference.scala │ │ │ └── exercises │ │ │ ├── HigherOrderFunctions.scala │ │ │ └── Recursion.scala │ ├── 05-folds-collections │ │ └── lazy-list │ │ │ ├── build.sbt │ │ │ ├── project │ │ │ ├── build.properties │ │ │ └── plugins.sbt │ │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ └── fmi │ │ │ └── collection │ │ │ └── LazyList.scala │ ├── 08-concurrency │ │ ├── build.sbt │ │ ├── project │ │ │ └── build.properties │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ ├── answers │ │ │ ├── HttpRequests.scala │ │ │ ├── LibraryClient.scala │ │ │ └── LibraryWebServer.scala │ │ │ ├── callbacks │ │ │ └── Callbacks.scala │ │ │ ├── concurrent │ │ │ ├── ExecutionContexts.scala │ │ │ ├── Executors.scala │ │ │ ├── future │ │ │ │ ├── Future.scala │ │ │ │ └── Promise.scala │ │ │ ├── io │ │ │ │ └── IO.scala │ │ │ └── lecture │ │ │ │ ├── Future.scala │ │ │ │ └── IO.scala │ │ │ ├── console │ │ │ ├── Console.scala │ │ │ └── ConsoleForIO.scala │ │ │ ├── http │ │ │ ├── FutureWebServer.scala │ │ │ ├── HttpClient.scala │ │ │ ├── HttpRequests.scala │ │ │ ├── LibraryClient.scala │ │ │ └── LibraryWebServer.scala │ │ │ ├── library │ │ │ └── Library.scala │ │ │ ├── product │ │ │ └── Product.scala │ │ │ ├── referentialtransparancy │ │ │ └── FutureReferentialTransparency.scala │ │ │ ├── threads │ │ │ ├── ThreadsExample.scala │ │ │ └── ThreadsSharingData.scala │ │ │ └── util │ │ │ ├── HttpServiceUrls.scala │ │ │ └── Utils.scala │ ├── 09-type-classes-scala-3 │ │ ├── build.sbt │ │ ├── project │ │ │ └── build.properties │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ ├── HelloWorld.scala │ │ │ ├── json │ │ │ ├── Json.scala │ │ │ ├── JsonDemo.scala │ │ │ └── Person.scala │ │ │ └── math │ │ │ ├── Monoid.scala │ │ │ ├── MonoidDemo.scala │ │ │ └── Rational.scala │ ├── 09-type-classes │ │ ├── build.sbt │ │ ├── other │ │ │ ├── monoid.hs │ │ │ ├── monoid.rs │ │ │ └── shapes.rs │ │ ├── project │ │ │ └── build.properties │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ ├── cats │ │ │ ├── CatsMonoidDemo.scala │ │ │ └── EqDemo.scala │ │ │ ├── json │ │ │ ├── Json.scala │ │ │ ├── JsonDemo.scala │ │ │ └── Person.scala │ │ │ ├── math │ │ │ ├── ListOrderingDemo.scala │ │ │ ├── Monoid.scala │ │ │ ├── MonoidDemo.scala │ │ │ ├── NumericTypeclassDemo.scala │ │ │ ├── OrderingTypeClassDemo.scala │ │ │ ├── Rational.scala │ │ │ └── Semigroup.scala │ │ │ ├── parallel │ │ │ └── ParallelCollections.scala │ │ │ ├── spire │ │ │ └── VectorSpaceDemo.scala │ │ │ └── types │ │ │ └── ClassMetainfoContextDemo.scala │ ├── 10-monads-and-applicatives │ │ ├── build.sbt │ │ ├── project │ │ │ └── build.properties │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ ├── effects │ │ │ ├── Applicative.scala │ │ │ ├── Functor.scala │ │ │ ├── Monad.scala │ │ │ ├── Traversable.scala │ │ │ ├── cats │ │ │ │ ├── FunctorCompositionDemo.scala │ │ │ │ └── MonadExample.scala │ │ │ ├── id │ │ │ │ └── Id.scala │ │ │ ├── maybe │ │ │ │ └── Maybe.scala │ │ │ └── state │ │ │ │ ├── RNG.scala │ │ │ │ ├── State.scala │ │ │ │ └── state-monad.md │ │ │ └── validation │ │ │ ├── DomainValidation.scala │ │ │ ├── FormValidation.scala │ │ │ ├── FormValidatorNec.scala │ │ │ ├── FormValidatorNecApplicative.scala │ │ │ └── RegistrationData.scala │ ├── 11-cats-and-cats-effects │ │ ├── build.sbt │ │ ├── project │ │ │ └── build.properties │ │ └── src │ │ │ ├── main │ │ │ ├── resources │ │ │ │ └── application.conf │ │ │ └── scala │ │ │ │ ├── cats │ │ │ │ ├── ApplyApplicativeTraverseDemo.scala │ │ │ │ ├── Composition.scala │ │ │ │ ├── ConcurrentParallelDemo.scala │ │ │ │ ├── DataTypesSyntax.scala │ │ │ │ ├── EqDemo.scala │ │ │ │ ├── FlatMapMonadMonadErrorDemo.scala │ │ │ │ ├── FoldableDemo.scala │ │ │ │ ├── FunctorDemo.scala │ │ │ │ ├── MonoidDemo.scala │ │ │ │ └── ParallelDemo.scala │ │ │ │ ├── io │ │ │ │ ├── Ex01RunningIO.scala │ │ │ │ ├── Ex02IOApp.scala │ │ │ │ ├── Ex03Fibers.scala │ │ │ │ ├── Ex04Cancellation.scala │ │ │ │ ├── Ex05Resource.scala │ │ │ │ └── Ex06SharedConcurrentAccess.scala │ │ │ │ ├── math │ │ │ │ └── Rational.scala │ │ │ │ └── user │ │ │ │ └── UserRegistration.scala │ │ │ └── test │ │ │ └── scala │ │ │ ├── io │ │ │ └── ChannelTestSuite.scala │ │ │ └── math │ │ │ └── RationalMonoidTest.scala │ ├── 12-building-a-scala-app │ │ ├── build.sbt │ │ ├── fahrenheit.txt │ │ ├── project │ │ │ └── build.properties │ │ └── src │ │ │ ├── main │ │ │ ├── resources │ │ │ │ ├── application.conf │ │ │ │ └── logback.xml │ │ │ └── scala │ │ │ │ ├── http │ │ │ │ ├── Example1_HttpRoutes.scala │ │ │ │ ├── Example2_PathParameters.scala │ │ │ │ ├── Example3_QueryParameters.scala │ │ │ │ ├── Example4_Responses.scala │ │ │ │ ├── Utils.scala │ │ │ │ ├── client │ │ │ │ │ ├── Joke.scala │ │ │ │ │ ├── JokeRouter.scala │ │ │ │ │ ├── JokeService.scala │ │ │ │ │ └── Main.scala │ │ │ │ ├── middlewares │ │ │ │ │ ├── SimpleMiddlewareExample.scala │ │ │ │ │ ├── auth │ │ │ │ │ │ ├── Auth.scala │ │ │ │ │ │ ├── AuthService.scala │ │ │ │ │ │ ├── Main.scala │ │ │ │ │ │ ├── User.scala │ │ │ │ │ │ └── UserDatabase.scala │ │ │ │ │ └── gzip │ │ │ │ │ │ └── GZipMiddlewareExample.scala │ │ │ │ └── tweets │ │ │ │ │ ├── Example5_EntityEncoders.scala │ │ │ │ │ └── Tweet.scala │ │ │ │ ├── io │ │ │ │ ├── Ex01RunningIO.scala │ │ │ │ ├── Ex02IOApp.scala │ │ │ │ ├── Ex03Fibers.scala │ │ │ │ ├── Ex04Cancellation.scala │ │ │ │ ├── Ex05Resource.scala │ │ │ │ └── Ex06SharedConcurrentAccess.scala │ │ │ │ ├── json │ │ │ │ ├── examples │ │ │ │ │ └── IdCard.scala │ │ │ │ └── semiauto │ │ │ │ │ └── Tweet.scala │ │ │ │ ├── modularity │ │ │ │ ├── MyApplication.scala │ │ │ │ ├── a │ │ │ │ │ ├── A1.scala │ │ │ │ │ ├── A2.scala │ │ │ │ │ ├── A3.scala │ │ │ │ │ └── AModule.scala │ │ │ │ ├── b │ │ │ │ │ ├── B1.scala │ │ │ │ │ ├── B2.scala │ │ │ │ │ └── BModule.scala │ │ │ │ ├── c │ │ │ │ │ ├── C.scala │ │ │ │ │ └── CModule.scala │ │ │ │ └── d │ │ │ │ │ └── D.scala │ │ │ │ ├── sql │ │ │ │ ├── Doobie01BasicExamples.scala │ │ │ │ ├── Doobie02Querying.scala │ │ │ │ ├── Doobie03Fragments.scala │ │ │ │ ├── Doobie04Updates.scala │ │ │ │ ├── Doobie05BatchUpdates.scala │ │ │ │ └── DoobieApp.scala │ │ │ │ └── streams │ │ │ │ ├── Fs201BasicExample.scala │ │ │ │ ├── Fs202Files.scala │ │ │ │ ├── Fs203Http.scala │ │ │ │ ├── Fs204HttpWithDoobie.scala │ │ │ │ └── Fs205WebSockets.scala │ │ │ └── test │ │ │ └── scala │ │ │ ├── http │ │ │ └── HelloSpec.scala │ │ │ └── io │ │ │ └── ChannelTestSuite.scala │ ├── 12-library-app │ │ ├── build.sbt │ │ ├── project │ │ │ └── build.properties │ │ └── src │ │ │ └── main │ │ │ ├── resources │ │ │ └── logback.xml │ │ │ └── scala │ │ │ └── fmi │ │ │ ├── LibraryClient.scala │ │ │ ├── LibraryServer.scala │ │ │ ├── client │ │ │ ├── LibraryApi.scala │ │ │ └── LibraryClientUI.scala │ │ │ ├── codecs │ │ │ └── LibraryCodecs.scala │ │ │ ├── library │ │ │ ├── BookSummary.scala │ │ │ └── Library.scala │ │ │ └── server │ │ │ └── LibraryHttpApp.scala │ ├── 12-shopping-app │ │ ├── build.sbt │ │ ├── project │ │ │ └── build.properties │ │ └── src │ │ │ └── main │ │ │ ├── resources │ │ │ ├── application.conf │ │ │ ├── db-migrations │ │ │ │ ├── V1.1__add_inventory_tables.sql │ │ │ │ ├── V1.2__add_order_tables.sql │ │ │ │ └── V1__init.sql │ │ │ └── logback.xml │ │ │ └── scala │ │ │ └── fmi │ │ │ ├── ShoppingApp.scala │ │ │ ├── config │ │ │ ├── HttpConfig.scala │ │ │ └── ShoppingAppConfig.scala │ │ │ ├── infrastructure │ │ │ ├── CryptoService.scala │ │ │ └── db │ │ │ │ ├── DbConfig.scala │ │ │ │ ├── DbMigrator.scala │ │ │ │ ├── DbModule.scala │ │ │ │ └── DoobieDatabase.scala │ │ │ ├── inventory │ │ │ ├── Inventory.scala │ │ │ ├── InventoryModule.scala │ │ │ ├── InventoryRouter.scala │ │ │ ├── Product.scala │ │ │ ├── ProductDao.scala │ │ │ └── ProductStockDao.scala │ │ │ ├── shopping │ │ │ ├── Order.scala │ │ │ ├── OrderDao.scala │ │ │ ├── OrderService.scala │ │ │ ├── ShippingRouter.scala │ │ │ ├── ShoppingCart.scala │ │ │ └── ShoppingModule.scala │ │ │ └── user │ │ │ ├── AuthenticationUtils.scala │ │ │ ├── PasswordUtils.scala │ │ │ ├── User.scala │ │ │ ├── UserRegistrationForm.scala │ │ │ ├── UsersDao.scala │ │ │ ├── UsersModule.scala │ │ │ ├── UsersRouter.scala │ │ │ └── UsersService.scala │ └── xx-akka-examples │ │ ├── build.sbt │ │ ├── project │ │ └── build.properties │ │ └── src │ │ └── main │ │ ├── resources │ │ └── logback.xml │ │ └── scala │ │ ├── actors │ │ ├── ActorsExample1.scala │ │ ├── ActorsExample2.scala │ │ └── ActorsExample3.scala │ │ └── typed │ │ ├── ActorsExample3.scala │ │ └── ChatRoomApp.scala ├── generate-presentation.sh ├── images │ ├── 01 │ │ ├── case-ended.webp │ │ ├── crown.jpg │ │ ├── grammar-size.png │ │ ├── greybeard.jpeg │ │ ├── lonely-island.gif │ │ ├── milewski.jpg │ │ ├── netflix-scala.jpg │ │ ├── ocado.png │ │ ├── odersky.JPG │ │ ├── pretending-to-write.gif │ │ ├── scala-logo.png │ │ ├── scala3-logo.jpeg │ │ ├── scala3-rc1.png │ │ └── static-type-system.jpeg │ ├── 02-fp-with-scala │ │ ├── functional-wizard.png │ │ ├── functions.png │ │ ├── java-memory-model-multithreaded.jpg │ │ ├── java-memory-model.jpg │ │ └── primitive-and-referenced-types.jpg │ ├── 03-oop-in-a-functional-language │ │ ├── alan-kay-raising-hand.png │ │ ├── alan-kay.jpg │ │ ├── covid-vaccine.webp │ │ └── messaging.png │ ├── 04-key-fp-approaches │ │ ├── building-blocks.webp │ │ ├── captain-obvious.jpg │ │ ├── chicken-curry.jpg │ │ ├── filter.png │ │ ├── lego-blocks.jpg │ │ ├── list-append.jpg │ │ ├── list.jpg │ │ ├── map.png │ │ ├── multple-lists.jpg │ │ ├── reduce.png │ │ ├── shared-objects.jpg │ │ ├── stack.jpg │ │ ├── vector-update.jpg │ │ └── vector.jpg │ ├── 08-concurrency │ │ ├── cpu-cache.jpeg │ │ └── dijkstra.jpg │ ├── 09-type-classes │ │ ├── category-theory-for-programmers.png │ │ ├── cats-cat.png │ │ ├── cats-small.png │ │ ├── cats.png │ │ ├── scala-with-cats.png │ │ └── vivian-boyan-cat.jpg │ ├── 10-monads-and-applicatives │ │ ├── big-cat-burrito.jpeg │ │ ├── category-theory-for-programmers.png │ │ ├── functional-programming-in-scala.jpeg │ │ └── impure-logo.png │ ├── 11-cats-and-cats-effects │ │ ├── hierarchy-impure.jpeg │ │ └── typelevel.svg │ ├── 12-building-a-scala-app │ │ └── circe-json-data-types.jpeg │ ├── Left-fold-transformation.png │ ├── Right-fold-transformation.png │ ├── actor_top_tree.png │ ├── akka_full_color.svg │ ├── animation-reverse.gif │ ├── arch_tree_diagram.png │ ├── but-why.gif │ ├── carl-hewitt.jpeg │ ├── classhierarchy.png │ ├── collections-diagram-213.svg │ ├── collections-diagram.svg │ ├── godji-opakovka2.jpg │ ├── hello-it.jpeg │ ├── joe-armstrong-quote.jpg │ ├── joe-armstrong.png │ ├── reduce.png │ ├── rob_norris.jpg │ ├── scala3-small.png │ ├── scala3.png │ ├── tpolecat_facepalm.jpg │ ├── whaaat.webp │ ├── work.png │ └── zipper.png ├── theme │ └── theme.css ├── xx-concurrency-with-akka.html └── xx-concurrency-with-akka.md ├── project-instructions.md └── resources ├── cats-cheat-sheet.md ├── type-elements-in-scala.md └── type-level-logic-using-implcits.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | 4 | .bsp 5 | target 6 | .metals 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lectures/reveal-js"] 2 | path = lectures/reveal-js 3 | url = https://github.com/hakimel/reveal.js.git 4 | -------------------------------------------------------------------------------- /lectures/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | shopt -s extglob 4 | 5 | for lecture_file in *.@(md|rst); do 6 | ./generate-presentation.sh "$lecture_file" 7 | done 8 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/examples/build.sbt: -------------------------------------------------------------------------------- 1 | name := "examples" 2 | version := "0.1" 3 | 4 | libraryDependencies ++= Seq( 5 | "org.scalatest" %% "scalatest" % "3.2.5" % Test 6 | ) 7 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/examples/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.4.7 2 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/examples/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3") 2 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/examples/src/main/scala/scalafmi/Balance.scala: -------------------------------------------------------------------------------- 1 | package scalafmi 2 | 3 | object Balance { 4 | def balanced(e: List[Char]): Boolean = ??? 5 | } 6 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/examples/src/main/scala/scalafmi/Examples.scala: -------------------------------------------------------------------------------- 1 | package scalafmi 2 | 3 | object Examples extends App { 4 | def toInteger(value: Any): Int = value match { 5 | case n: Int => n 6 | case s: String => s.toInt 7 | case d: Double => d.toInt 8 | } 9 | 10 | def square(x: Double) = x * x 11 | square(2) 12 | toInteger(2) 13 | 14 | toInteger(Array(1, 2, 3)) 15 | 16 | def sum(xs: Seq[Int]): Int = 17 | if (xs.isEmpty) 0 18 | else xs.head + sum(xs.tail) 19 | 20 | println { 21 | sum(List.empty) 22 | } 23 | 24 | def enlist(first: String, rest: List[String]) = (first +: rest).mkString(", ") 25 | 26 | enlist("a", List("b", "c")) 27 | } 28 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/examples/src/main/scala/scalafmi/Expressions.scala: -------------------------------------------------------------------------------- 1 | package scalafmi 2 | 3 | object Expressions { 4 | def balanced(e: List[Char]): Boolean = ??? 5 | } 6 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/examples/src/main/scala/scalafmi/FunctionalVsImperative.scala: -------------------------------------------------------------------------------- 1 | package scalafmi 2 | 3 | object FunctionalVsImperative extends App { 4 | def fn = { 5 | // with lazy val any order works 6 | // 7 | // with just val we will get compile error if something is not right 8 | // (it won't succeed silently like in imperative programis) 9 | 10 | lazy val y = b * 40 11 | lazy val c = 50 12 | 13 | lazy val a = 10 14 | lazy val z = y * x * x 15 | 16 | lazy val b = 40 17 | lazy val x = a + c 18 | 19 | z 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/examples/src/main/scala/scalafmi/Substrings.scala: -------------------------------------------------------------------------------- 1 | package scalafmi 2 | 3 | object Substrings extends App { 4 | def substrings(str: String): Seq[String] = for { 5 | i <- 0 until str.size 6 | j <- 1 to str.size - i 7 | } yield str.drop(i).take(j) 8 | 9 | println { 10 | substrings("abc") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/hello-scala-3/build.sbt: -------------------------------------------------------------------------------- 1 | name := "hello-scala-3" 2 | version := "0.1" 3 | scalaVersion := "3.0.0-RC1" 4 | 5 | libraryDependencies ++= Seq( 6 | "org.scalatest" %% "scalatest" % "3.2.5" % Test 7 | ) 8 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/hello-scala-3/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.4.7 2 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/hello-scala-3/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3") 2 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/hello-scala-3/src/main/scala/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | @main def hello = println("Hello, World!") 2 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/hello-scala-3/src/test/scala/ExampleSpec.scala: -------------------------------------------------------------------------------- 1 | import org.scalatest.flatspec.AnyFlatSpec 2 | import org.scalatest.matchers.should.Matchers 3 | 4 | class ExampleSpec extends AnyFlatSpec with Matchers { 5 | "+" should "sum two numbers" in { 6 | 2 + 3 should be (5) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/hello-world/build.sbt: -------------------------------------------------------------------------------- 1 | name := "hello-world" 2 | version := "0.1" 3 | 4 | libraryDependencies ++= Seq( 5 | "org.scalatest" %% "scalatest" % "3.2.5" % Test 6 | ) 7 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/hello-world/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.4.7 2 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/hello-world/src/main/scala/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | object HelloWorld { 2 | def main(args: Array[String]): Unit = { 3 | println("Hello, World!") 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/hello-world/src/test/scala/ExampleSpec.scala: -------------------------------------------------------------------------------- 1 | import org.scalatest.flatspec.AnyFlatSpec 2 | import org.scalatest.matchers.should.Matchers 3 | 4 | class ExampleSpec extends AnyFlatSpec with Matchers { 5 | "+" should "sum two numbers" in { 6 | 2 + 3 should be (5) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/build.sbt: -------------------------------------------------------------------------------- 1 | name := "oop-in-a-functional-language" 2 | version := "0.1" 3 | 4 | scalaVersion := "2.13.5" 5 | //scalaVersion := "3.0.0-RC1" 6 | 7 | libraryDependencies ++= Seq( 8 | "org.scalatest" %% "scalatest" % "3.2.5" % Test 9 | ) 10 | -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.4.7 2 | -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3") 2 | -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/src/main/scala/expressionproblem/fp/Shape.scala: -------------------------------------------------------------------------------- 1 | package expressionproblem.fp 2 | 3 | trait Shape 4 | case class Circle(r: Double) extends Shape 5 | case class Rectangle(a: Double, b: Double) extends Shape 6 | 7 | object Shape { 8 | def area(s: Shape): Double = s match { 9 | case Circle(r) => math.Pi * r * r 10 | case Rectangle(a, b) => a * b 11 | } 12 | } -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/src/main/scala/expressionproblem/oop/Shape.scala: -------------------------------------------------------------------------------- 1 | package expressionproblem.oop 2 | 3 | trait Shape { 4 | def area: Double 5 | } 6 | 7 | case class Circle(r: Double) extends Shape { 8 | def area: Double = math.Pi * r * r 9 | } 10 | 11 | case class Rectangle(a: Double, b: Double) extends Shape { 12 | def area: Double = a * b 13 | } 14 | -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/src/main/scala/mathematical/Rational.scala: -------------------------------------------------------------------------------- 1 | package mathematical 2 | 3 | class Rational -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/src/main/scala/solutions/mathematical/v1/Rational.scala: -------------------------------------------------------------------------------- 1 | package solutions.mathematical.v1 2 | 3 | class Rational(n: Int, d: Int = 1) { 4 | require(d != 0) 5 | 6 | val (numer, denom) = { 7 | val div = gcd(n.abs, d.abs) 8 | 9 | (d.sign * n / div, d.abs / div) 10 | } 11 | 12 | def unary_- = new Rational(-numer, denom) 13 | def unary_~ = new Rational(denom, numer) 14 | 15 | def +(that: Rational) = new Rational( 16 | numer * that.denom + that.numer * denom, 17 | denom * that.denom 18 | ) 19 | 20 | def -(that: Rational) = this + (-that) 21 | 22 | def *(that: Rational) = new Rational(numer * that.numer, denom * that.denom) 23 | 24 | def /(that: Rational) = this * (~that) 25 | 26 | override def hashCode: Int = (numer, denom).hashCode 27 | 28 | override def equals(obj: Any): Boolean = obj match { 29 | case that: Rational => numer == that.numer && denom == that.denom 30 | case _ => false 31 | } 32 | 33 | override def toString: String = s"$numer/$denom" 34 | 35 | private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) 36 | } 37 | -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/src/main/scala/solutions/mathematical/v2/Rational.scala: -------------------------------------------------------------------------------- 1 | package solutions.mathematical.v2 2 | 3 | import scala.language.implicitConversions 4 | 5 | class Rational(n: Int, d: Int) extends Ordered[Rational] { 6 | require(d != 0) 7 | 8 | val (numer, denom) = { 9 | val div = gcd(n.abs, d.abs) 10 | 11 | (d.sign * n / div, d.abs / div) 12 | } 13 | 14 | def unary_- = Rational(-numer, denom) 15 | 16 | def unary_~ = Rational(denom, numer) 17 | def +(that: Rational) = Rational( 18 | numer * that.denom + that.numer * denom, 19 | denom * that.denom 20 | ) 21 | 22 | def -(that: Rational) = this + (-that) 23 | 24 | def *(that: Rational) = Rational(numer * that.numer, denom * that.denom) 25 | 26 | def /(that: Rational) = this * (~that) 27 | 28 | override def hashCode: Int = (numer, denom).hashCode 29 | 30 | override def equals(obj: Any): Boolean = obj match { 31 | case that: Rational => numer == that.numer && denom == that.denom 32 | case _ => false 33 | } 34 | 35 | override def toString: String = s"$numer/$denom" 36 | 37 | def compare(that: Rational): Int = (this - that).numer 38 | 39 | private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) 40 | } 41 | 42 | object Rational { 43 | def apply(n: Int, d: Int = 1) = new Rational(n, d) 44 | 45 | implicit def intToRational(n: Int): Rational = Rational(n) 46 | } 47 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/build.sbt: -------------------------------------------------------------------------------- 1 | name := "key-fp-approaches" 2 | version := "0.1" 3 | 4 | scalaVersion := "2.13.5" 5 | //scalaVersion := "3.0.0-RC1" 6 | 7 | libraryDependencies ++= Seq( 8 | "org.scalatest" %% "scalatest" % "3.2.5" % Test 9 | ) 10 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.4.3 2 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | //addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3") 2 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/src/main/scala/answers/HigherOrderFunctions.scala: -------------------------------------------------------------------------------- 1 | package answers 2 | 3 | import scala.annotation.tailrec 4 | 5 | object HigherOrderFunctions { 6 | 7 | def filter[A](la: List[A], p: A => Boolean): List[A] = { 8 | @tailrec 9 | def loop(rest: List[A], acc: List[A] = Nil): List[A] = { 10 | if (rest.isEmpty) acc 11 | else if (p(rest.head)) loop(rest.tail, rest.head :: acc) 12 | else loop(rest.tail, acc) 13 | } 14 | 15 | loop(la).reverse 16 | } 17 | 18 | def map[A, B](la: List[A], f: A => B): List[B] = { 19 | @tailrec 20 | def loop(rest: List[A], acc: List[B] = Nil): List[B] = 21 | if (rest.isEmpty) acc 22 | else loop(rest.tail, f(rest.head) :: acc) 23 | 24 | loop(la).reverse 25 | } 26 | 27 | def reduce[A](la: List[A], f: (A, A) => A): A = { 28 | @tailrec 29 | def loop(rest: List[A], acc: A): A = 30 | if (rest.isEmpty) acc 31 | else loop(rest.tail, f(rest.head, acc)) 32 | 33 | loop(la.tail, la.head) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/src/main/scala/answers/multiparameters/GroupingOfParameters.scala: -------------------------------------------------------------------------------- 1 | package answers.multiparameters 2 | 3 | object GroupingOfParameters { 4 | def min[T](compare: (T, T) => Int)(a: T, b: T): T = 5 | if (compare(a, b) < 0) a 6 | else b 7 | 8 | val compareByAbsoluteValue = (a: Int, b: Int) => a.abs - b.abs 9 | val minByAbsoluteValue = min(compareByAbsoluteValue) _ 10 | 11 | min(Integer.compare)(20, 10) // 10 12 | minByAbsoluteValue(-20, -10) // -10 13 | List(-20, -10, 40).reduce(minByAbsoluteValue) // -10 14 | } 15 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/src/main/scala/answers/multiparameters/LanguageConstructs.scala: -------------------------------------------------------------------------------- 1 | package answers.multiparameters 2 | 3 | import answers.TailRecursion.{fact, fibonacci} 4 | import answers.multiparameters.TypeInference.mapML 5 | 6 | import scala.annotation.tailrec 7 | import scala.io.Source 8 | import scala.language.reflectiveCalls 9 | 10 | object LanguageConstructs extends App { 11 | // times construct 12 | implicit class EnrichedInt(val n: Int) extends AnyVal { 13 | def times(fn: => Unit): Unit = 14 | if (n <= 0) () 15 | else { 16 | fn 17 | (n - 1).times(fn) 18 | } 19 | } 20 | 21 | 4.times { 22 | println("Meow") 23 | println("I am a cat") 24 | } 25 | 26 | @tailrec 27 | def times(n: Int)(fn: => Unit): Unit = 28 | if (n <= 0) () 29 | else { 30 | fn 31 | times(n - 1)(fn) 32 | } 33 | 34 | times(4) { 35 | println("Meow") 36 | println("I am a cat") 37 | } 38 | 39 | // Higher-ordered functions as constructs 40 | mapML(List(4, 5, 6)) { n => 41 | val factN = fact(n) 42 | val fibN = fibonacci(n) 43 | 44 | factN + fibN 45 | } 46 | 47 | List(20, -40, 30).fold(0) { (a, b) => 48 | math.max(a.abs, b.abs) 49 | } 50 | 51 | // safe resources 52 | type Closeable = { def close(): Unit } 53 | 54 | def using[A <: Closeable, B](resource: A)(f: A => B): B = 55 | try f(resource) 56 | finally resource.close() // requires the reflectiveCalls import. Using java.lang.AutoCloseable might be better here 57 | 58 | def numberOfLines(fileName: String): Int = 59 | using(Source.fromFile(fileName)) { file => 60 | file.getLines().size 61 | } 62 | 63 | println { 64 | numberOfLines("build.sbt") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/src/main/scala/answers/multiparameters/TypeInference.scala: -------------------------------------------------------------------------------- 1 | package answers.multiparameters 2 | 3 | import scala.annotation.tailrec 4 | 5 | object TypeInference { 6 | def mapSL[A, B](la: List[A], f: A => B): List[B] = { 7 | @tailrec 8 | def loop(rest: List[A], acc: List[B] = Nil): List[B] = 9 | if (rest.isEmpty) acc 10 | else loop(rest.tail, f(rest.head) :: acc) 11 | 12 | loop(la).reverse 13 | } 14 | 15 | def mapML[A, B](la: List[A])(f: A => B): List[B] = { 16 | @tailrec 17 | def loop(rest: List[A], acc: List[B] = Nil): List[B] = 18 | if (rest.isEmpty) acc 19 | else loop(rest.tail, f(rest.head) :: acc) 20 | 21 | loop(la).reverse 22 | } 23 | 24 | // mapSL(List(1, 2, 3), _ * 2) // Does not compile 25 | mapSL(List(1, 2, 3), (_: Int) * 2) 26 | 27 | mapML(List(1, 2, 3))(_ * 2) // Type inference works 28 | mapML(List(1, 2, 3))((_: Int) * 2) 29 | } 30 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/src/main/scala/exercises/HigherOrderFunctions.scala: -------------------------------------------------------------------------------- 1 | package exercises 2 | 3 | object HigherOrderFunctions { 4 | 5 | def filter[A](la: List[A], p: A => Boolean): List[A] = ??? 6 | 7 | def map[A, B](la: List[A], f: A => B): List[B] = ??? 8 | 9 | def reduce[A](la: List[A], f: (A, A) => A): A = ??? 10 | 11 | } 12 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/src/main/scala/exercises/Recursion.scala: -------------------------------------------------------------------------------- 1 | package exercises 2 | 3 | import answers.TailRecursion.fibonacci 4 | import exercises.Recursion.fibonacci 5 | 6 | import scala.annotation.tailrec 7 | 8 | object Recursion extends App { 9 | 10 | def fact(n: Int): Int = 11 | if (n <= 1) 1 12 | else n * fact(n - 1) 13 | 14 | def size[A](l: List[A]): Int = 15 | if (l.isEmpty) 0 16 | else 1 + size(l.tail) 17 | 18 | def sum(l: List[Int]): Int = 19 | if (l.isEmpty) 0 20 | else l.head + sum(l.tail) 21 | 22 | //f(5) = f(4) + f(3) 23 | def fibonacci(i: Int): Int = 24 | if (i <= 1) 1 25 | else fibonacci(i - 1) + fibonacci(i - 2) 26 | } 27 | 28 | object TailRecursion extends App { 29 | 30 | // We could introduce inner functions if we don't want to pollute the interface 31 | // But let's use default parameters 32 | 33 | @tailrec 34 | def fact(n: Int, acc: Int = 1): Int = 35 | if (n <= 1) acc 36 | else fact(n - 1, acc * n) 37 | 38 | @tailrec 39 | def size[A](l: List[A], acc: Int = 0): Int = 40 | if (l.isEmpty) acc 41 | else size(l.tail, acc + 1) 42 | 43 | @tailrec 44 | def sum(l: List[Int], acc: Int = 0): Int = 45 | if (l.isEmpty) acc 46 | else sum(l.tail, acc + l.head) 47 | 48 | @tailrec 49 | def fibonacci(i: Int, prev: Int = 0, current: Int = 1): Int = 50 | if (i <= 1) current 51 | else fibonacci(i - 1, prev = current, current = current + prev) 52 | 53 | } 54 | 55 | object MoreListFunctions extends App { 56 | 57 | @tailrec 58 | def drop[A](la: List[A], n: Int): List[A] = 59 | if (n <= 0 || la.isEmpty) la 60 | else drop(la.tail, n - 1) 61 | 62 | @tailrec 63 | def reverse[A](l: List[A], acc: List[A] = Nil): List[A] = 64 | if (l.isEmpty) acc 65 | else reverse(l.tail, l.head :: acc) 66 | 67 | def take[A](la: List[A], n: Int): List[A] = ??? 68 | 69 | def nthElement[A](la: List[A], n: Int): A = ??? 70 | 71 | def concat(l1: List[Int], l2: List[Int]): List[Int] = ??? 72 | } 73 | -------------------------------------------------------------------------------- /lectures/examples/05-folds-collections/lazy-list/build.sbt: -------------------------------------------------------------------------------- 1 | name := "lazy-list" 2 | version := "0.1" 3 | 4 | scalaVersion := "2.13.5" 5 | //scalaVersion := "3.0.0-RC1" 6 | 7 | libraryDependencies ++= Seq( 8 | "org.scalatest" %% "scalatest" % "3.2.5" % Test 9 | ) 10 | -------------------------------------------------------------------------------- /lectures/examples/05-folds-collections/lazy-list/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.4.3 2 | -------------------------------------------------------------------------------- /lectures/examples/05-folds-collections/lazy-list/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | //addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3") 2 | -------------------------------------------------------------------------------- /lectures/examples/05-folds-collections/lazy-list/src/main/scala/fmi/collection/LazyList.scala: -------------------------------------------------------------------------------- 1 | package fmi.collection 2 | 3 | import scala.annotation.tailrec 4 | 5 | import Function.tupled 6 | 7 | sealed trait LazyList[+A] { 8 | def head: A 9 | def tail: LazyList[A] 10 | 11 | def isEmpty: Boolean 12 | 13 | def take(n: Int): LazyList[A] = 14 | if (n == 0) LazyNil 15 | else LazyCons(head, tail.take(n - 1)) 16 | 17 | def map[B](f: A => B): LazyList[B] = 18 | if (isEmpty) LazyNil 19 | else LazyCons(f(head), tail.map(f)) 20 | 21 | def zip[B](that: LazyList[B]): LazyList[(A, B)] = 22 | if (this.isEmpty || that.isEmpty) LazyNil 23 | else LazyCons((this.head, that.head), this.tail zip that.tail) 24 | 25 | def toList: List[A] = { 26 | @tailrec 27 | def loop(xs: LazyList[A], acc: List[A]): List[A] = { 28 | if (xs.isEmpty) acc.reverse 29 | else loop(xs.tail, xs.head :: acc) 30 | } 31 | 32 | loop(this, Nil) 33 | } 34 | } 35 | 36 | class LazyCons[+A](h: => A, t: => LazyList[A]) extends LazyList[A] { 37 | lazy val head: A = h 38 | lazy val tail: LazyList[A] = t 39 | 40 | def isEmpty: Boolean = false 41 | } 42 | 43 | object LazyNil extends LazyList[Nothing] { 44 | def head: Nothing = throw new NoSuchElementException 45 | def tail: LazyList[Nothing] = throw new UnsupportedOperationException 46 | 47 | def isEmpty: Boolean = true 48 | } 49 | 50 | object LazyCons { 51 | def apply[A](h: => A, t: => LazyList[A]): LazyCons[A] = new LazyCons[A](h, t) 52 | } 53 | 54 | object LazyList { 55 | implicit class LazyListOps[A](list: => LazyList[A]) { 56 | def #::(el: => A): LazyList[A] = LazyCons(el, list) 57 | } 58 | 59 | def from(start: Int, step: Int = 1): LazyList[Int] = LazyCons(start, from(start + step, step)) 60 | val naturalNumbers: LazyList[Int] = from(0) 61 | 62 | val fibs: LazyList[Int] = 0 #:: 1 #:: (fibs zip fibs.tail).map(tupled(_ + _)) 63 | fibs.take(10).toList 64 | } 65 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/build.sbt: -------------------------------------------------------------------------------- 1 | name := "concurrency" 2 | version := "0.1" 3 | 4 | scalaVersion := "2.13.5" 5 | 6 | val akkaVersion = "2.6.13" 7 | 8 | libraryDependencies ++= Seq( 9 | "io.monix" %% "monix" % "3.3.0", 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-stream" % akkaVersion, 13 | "com.typesafe.akka" %% "akka-http" % "10.2.4", 14 | 15 | "org.asynchttpclient" % "async-http-client" % "2.12.3", 16 | 17 | "io.monix" %% "monix-eval" % "3.3.0", 18 | 19 | "org.scalatest" %% "scalatest" % "3.2.5" % Test 20 | ) 21 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.0 2 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/answers/HttpRequests.scala: -------------------------------------------------------------------------------- 1 | package answers 2 | 3 | import http.HttpClient 4 | import util.HttpServiceUrls 5 | 6 | object HttpRequests extends App { 7 | import concurrent.Executors.defaultExecutor 8 | 9 | val result = for { 10 | (a, b) <- HttpClient.get("https://google.com").zip(HttpClient.get("https://www.scala-lang.org/")) 11 | 12 | aLength = a.getResponseBody.length 13 | bLength = b.getResponseBody.length 14 | sum = aLength + bLength 15 | 16 | randomNumber <- HttpClient.get(HttpServiceUrls.randomNumberUpTo(sum)) 17 | } yield randomNumber.getResponseBody 18 | 19 | result.foreach(println) 20 | } 21 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/callbacks/Callbacks.scala: -------------------------------------------------------------------------------- 1 | package callbacks 2 | 3 | import product.{Product, ProductFactory, Verification} 4 | 5 | import java.util.concurrent.{Executor, Executors} 6 | 7 | object Callbacks { 8 | val threadPool: Executor = Executors.newFixedThreadPool(Runtime.getRuntime.availableProcessors) 9 | def execute(work: => Any): Unit = threadPool.execute(() => work) 10 | 11 | def produceProduct(onComplete: Product => Unit): Unit = execute { 12 | val product = ProductFactory.produceProduct("Book") 13 | 14 | execute(onComplete(product)) 15 | } 16 | 17 | def produce2Products(onComplete: (Product, Product) => Unit): Unit = { 18 | var firstProduct: Option[Product] = None 19 | 20 | def callback(newProduct: Product): Unit = this.synchronized { 21 | firstProduct match { 22 | case None => 23 | firstProduct = Some(newProduct) 24 | case Some(existingProduct) => 25 | onComplete(existingProduct, newProduct) 26 | } 27 | } 28 | 29 | produceProduct(callback) 30 | produceProduct(callback) 31 | } 32 | 33 | def main(args: Array[String]): Unit = execute { 34 | produce2Products((p1, p2) => println((p1, p2))) 35 | } 36 | 37 | def verifyProduct(product: Product)(onVerified: Verification => Unit): Unit = execute { 38 | val verification = ProductFactory.verifyProduct(product) 39 | 40 | execute(onVerified(verification)) 41 | } 42 | 43 | def produceInPipeline(callback: (List[Product], List[Verification]) => Unit): Unit = { 44 | // Callback hell 45 | produceProduct { a => 46 | verifyProduct(a) { aVerification => 47 | produceProduct { b => 48 | verifyProduct(b) { bVerification => 49 | callback(List(a, b), List(aVerification, bVerification)) 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/concurrent/ExecutionContexts.scala: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import java.util.concurrent.Executors.newCachedThreadPool 4 | import java.util.concurrent.{Executor, ForkJoinPool} 5 | import scala.concurrent.ExecutionContext 6 | 7 | object ExecutionContexts { 8 | implicit val default: ExecutionContext = ExecutionContext.fromExecutorService(new ForkJoinPool) 9 | 10 | val currentThreadEc = ExecutionContext.fromExecutor { 11 | (operation: Runnable) => operation.run() 12 | } 13 | 14 | val blocking = ExecutionContext.fromExecutorService(newCachedThreadPool()) 15 | } 16 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/concurrent/Executors.scala: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import java.util.concurrent.{Executor, ForkJoinPool} 4 | 5 | object Executors { 6 | implicit val defaultExecutor: Executor = new ForkJoinPool 7 | 8 | val currentThreadExecutor = new Executor { 9 | override def execute(operation: Runnable): Unit = operation.run() 10 | } 11 | 12 | val blockingExecutor = java.util.concurrent.Executors.newCachedThreadPool() 13 | } 14 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/concurrent/lecture/Future.scala: -------------------------------------------------------------------------------- 1 | package concurrent.lecture 2 | 3 | import concurrent.Executors 4 | import product.ProductFactory.produceProduct 5 | 6 | import java.util.concurrent.Executor 7 | import scala.util.Try 8 | 9 | trait Future[A] { 10 | def value: Option[Try[A]] 11 | 12 | def onComplete(f: Try[A] => Unit)(implicit ex: Executor): Unit 13 | 14 | def isCompleted: Boolean = value.isDefined 15 | 16 | def map[B](f: A => B)(implicit ex: Executor): Future[B] 17 | def flatMap[B](f: A => Future[B])(implicit ex: Executor): Future[B] 18 | 19 | def zip[B](other: Future[B])(implicit ex: Executor): Future[(A, B)] 20 | def zipMap[B, C](other: Future[B])(f: (A, B) => C)(implicit ex: Executor): Future[C] 21 | 22 | // filter, transform, 23 | 24 | // see concurrent.future for complete implementation 25 | } 26 | 27 | object Future { 28 | def apply[A](value: => A)(implicit ex: Executor): Future[A] = ??? 29 | 30 | import Executors.defaultExecutor 31 | 32 | Future(produceProduct("Book")) 33 | .map(_.name) 34 | } 35 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/console/Console.scala: -------------------------------------------------------------------------------- 1 | package console 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | import scala.io.StdIn 5 | 6 | class Console(blockingEc: ExecutionContext) { 7 | def getStringLine: Future[String] = Future(StdIn.readLine())(blockingEc) 8 | def putStringLine(str: String): Future[Unit] = Future(println(str))(blockingEc) 9 | } 10 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/console/ConsoleForIO.scala: -------------------------------------------------------------------------------- 1 | package console 2 | 3 | import concurrent.io.IO 4 | 5 | import scala.concurrent.ExecutionContext 6 | import scala.io.StdIn 7 | 8 | class ConsoleForIO(blockingEc: ExecutionContext) { 9 | def getStringLine: IO[String] = IO(StdIn.readLine()).bindTo(blockingEc) 10 | def putStringLine(str: String): IO[Unit] = IO(println(str)).bindTo(blockingEc) 11 | } 12 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/http/FutureWebServer.scala: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import akka.actor.ActorSystem 4 | import akka.http.scaladsl.Http 5 | import akka.http.scaladsl.model.{HttpResponse, StatusCode, StatusCodes} 6 | import akka.http.scaladsl.server.Directives._ 7 | import akka.http.scaladsl.server.Route 8 | import library.{BookId, Library} 9 | import util.Utils 10 | 11 | import scala.concurrent.Future 12 | 13 | object FutureWebServer { 14 | implicit val actorSystem = ActorSystem() 15 | implicit val ec = actorSystem.dispatcher 16 | 17 | def doWork() = Future { 18 | Utils.doWork 19 | Utils.doWork 20 | 21 | 42 22 | } 23 | 24 | val routes: Route = (get & path("do-work")) { 25 | complete(doWork().map(_.toString)) 26 | } ~ (get & path("books" / Segment / "name")) { bookIdString => 27 | complete(Library.TheGreatLibrary.findBook(BookId(bookIdString)).map { 28 | case Some(book) => HttpResponse(entity = book.name) 29 | case None => HttpResponse(StatusCodes.NotFound) 30 | }) 31 | } 32 | 33 | def main(args: Array[String]): Unit = { 34 | Http().newServerAt("0.0.0.0", 8080).bindFlow(routes) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/http/HttpClient.scala: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import concurrent.future.{Future, Promise} 4 | import concurrent.io.IO 5 | import monix.eval.Task 6 | import org.asynchttpclient.Dsl._ 7 | import org.asynchttpclient._ 8 | 9 | import scala.concurrent.ExecutionContext 10 | import scala.util.Try 11 | 12 | object HttpClient { 13 | val client = asyncHttpClient() 14 | 15 | def get(url: String): Future[Response] = { 16 | val p = Promise[Response] 17 | 18 | println("Getting " + url) 19 | 20 | val eventualResponse = client.prepareGet(url).setFollowRedirect(true).execute() 21 | eventualResponse.addListener(() => p.complete(Try(eventualResponse.get())), null) 22 | eventualResponse.addListener(() => println(s"Finished getting $url"), null) 23 | 24 | p.future 25 | } 26 | 27 | def getIO(url: String): IO[Response] = { 28 | val eventualResponse = client.prepareGet(url).setFollowRedirect(true).execute() 29 | 30 | ExecutionContext 31 | 32 | IO.usingCallback[Response] { (ec, callback) => 33 | eventualResponse.addListener(() => callback(Try(eventualResponse.get())), ec.execute) 34 | } 35 | } 36 | 37 | def getScalaFuture(url: String): scala.concurrent.Future[Response] = { 38 | val p = scala.concurrent.Promise[Response]() 39 | 40 | val eventualResponse = client.prepareGet(url).setFollowRedirect(true).execute() 41 | eventualResponse.addListener(() => p.complete(Try(eventualResponse.get())), null) 42 | 43 | p.future 44 | } 45 | } 46 | 47 | case class BadResponse(statusCode: Int) extends Exception 48 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/http/HttpRequests.scala: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import util.HttpServiceUrls 4 | 5 | object HttpRequests extends App { 6 | import concurrent.Executors.defaultExecutor 7 | 8 | val exampleRequest = HttpClient.get("http://example.com").map(_.getResponseBody) 9 | val googleRequest = HttpClient.get("http://google.com").map(_.getResponseBody) 10 | 11 | val result = for { 12 | (exampleResponse, googleResponse) <- exampleRequest zip googleRequest 13 | randomNumberUrl = HttpServiceUrls.randomNumberUpTo(exampleResponse.length + googleResponse.length) 14 | randomNumberResponse <- HttpClient.get(randomNumberUrl) 15 | } yield randomNumberResponse.getResponseBody 16 | 17 | result.foreach(println) 18 | } 19 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/library/Library.scala: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import scala.concurrent.Future 4 | 5 | case class BookId(id: String) extends AnyVal 6 | case class Book(id: BookId, name: String, authors: List[AuthorId], genre: String) 7 | 8 | case class AuthorId(id: String) extends AnyVal 9 | case class Author(id: AuthorId, name: String) 10 | 11 | class Library(books: List[Book], authors: List[Author]) { 12 | private val bookIdToBook = books.map(book => book.id -> book).toMap 13 | private val authorIdToAuthor = authors.map(author => author.id -> author).toMap 14 | 15 | def findBook(bookId: BookId): Future[Option[Book]] = Future.successful(bookIdToBook.get(bookId)) 16 | 17 | def findAuthor(authorId: AuthorId): Future[Option[Author]] = Future.successful(authorIdToAuthor.get(authorId)) 18 | 19 | def allBooks: Future[Set[BookId]] = Future.successful(bookIdToBook.keySet) 20 | } 21 | 22 | object Library { 23 | private val books = List( 24 | Book(BookId("1"), "Programming in Scala", List(AuthorId("1"), AuthorId("2")), "Computer Science"), 25 | Book(BookId("2"), "Programming Erlang", List(AuthorId("3")), "Computer Science"), 26 | Book(BookId("3"), "American Gods", List(AuthorId("4")), "Fantasy"), 27 | Book(BookId("4"), "The Fellowship of the Ring", List(AuthorId("5")), "Fantasy"), 28 | Book(BookId("5"), "The Book", List( 29 | AuthorId("1"), AuthorId("3"), AuthorId("4"), AuthorId("5") 30 | ), "Fantasy") 31 | ) 32 | private val authors = List( 33 | Author(AuthorId("1"), "Martin Odersky"), 34 | Author(AuthorId("2"), "Bill Venners"), 35 | Author(AuthorId("3"), "Joe Armstrong"), 36 | Author(AuthorId("4"), "Neil Gaiman"), 37 | Author(AuthorId("5"), "J. R. R. Tolkien") 38 | ) 39 | 40 | val TheGreatLibrary = new Library(books, authors) 41 | } 42 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/product/Product.scala: -------------------------------------------------------------------------------- 1 | package product 2 | 3 | case class Product(name: String, kind: String) 4 | case class Verification(quality: Int) 5 | 6 | object ProductFactory { 7 | def produceProduct(kind: String): Product = { 8 | val productId = Thread.currentThread().getId 9 | 10 | println(s"Producing product $productId...") 11 | Thread.sleep(2000) 12 | println(s"Product produced, $productId") 13 | 14 | Product(s"Product $productId", kind) 15 | } 16 | 17 | def verifyProduct(product: Product): Verification = { 18 | val threadId = Thread.currentThread().getId 19 | 20 | println(s"Verifying product ${product.name}...") 21 | Thread.sleep(2000) 22 | println(s"Product verified, thread: $threadId") 23 | 24 | Verification(threadId.hashCode) 25 | } 26 | } -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/referentialtransparancy/FutureReferentialTransparency.scala: -------------------------------------------------------------------------------- 1 | package referentialtransparancy 2 | 3 | import scala.concurrent.ExecutionContext.Implicits.global 4 | import scala.concurrent.duration.DurationInt 5 | import scala.concurrent.{Await, Future} 6 | 7 | object FutureReferentialTransparency extends App { 8 | def calc[T](expr: => T) = Future { 9 | Thread.sleep(4000) 10 | 11 | expr 12 | } 13 | 14 | val sum = for { 15 | (a, b) <- calc(42) zip calc(10) 16 | } yield a + b 17 | 18 | println { 19 | Await.result(sum, 5.seconds) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/threads/ThreadsExample.scala: -------------------------------------------------------------------------------- 1 | package threads 2 | 3 | import util.Utils 4 | 5 | object ThreadsExample extends App { 6 | def doWork = Utils.doWork 7 | 8 | def createThread(work: => Unit) = new Thread(() => work) 9 | 10 | Utils.time("Without threads") { 11 | doWork 12 | doWork 13 | doWork 14 | doWork 15 | doWork 16 | doWork 17 | doWork 18 | doWork 19 | } 20 | 21 | Utils.time("With threads") { 22 | val thread1 = createThread(doWork) 23 | val thread2 = createThread(doWork) 24 | val thread3 = createThread(doWork) 25 | val thread4 = createThread(doWork) 26 | val thread5 = createThread(doWork) 27 | val thread6 = createThread(doWork) 28 | val thread7 = createThread(doWork) 29 | val thread8 = createThread(doWork) 30 | 31 | thread1.start() 32 | thread2.start() 33 | thread3.start() 34 | thread4.start() 35 | thread5.start() 36 | thread6.start() 37 | thread7.start() 38 | thread8.start() 39 | 40 | thread1.join() 41 | thread2.join() 42 | thread3.join() 43 | thread4.join() 44 | thread5.join() 45 | thread6.join() 46 | thread7.join() 47 | thread8.join() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/threads/ThreadsSharingData.scala: -------------------------------------------------------------------------------- 1 | package threads 2 | 3 | object ThreadsSharingData extends App { 4 | // Нишката thread по-долу не вижда промените по тази променлива 5 | // Ако тя се маркира като @volatila тогава видимостта ще сработи. 6 | var improveCalculation = true 7 | 8 | val thread = new Thread(() => { 9 | var i = 0L 10 | 11 | while (improveCalculation) { 12 | i += 1 13 | } 14 | 15 | println(s"Thread exiting: $i") 16 | }) 17 | 18 | thread.start() 19 | 20 | println("Main going to sleep...") 21 | Thread.sleep(1000) 22 | println("Main waking up...") 23 | 24 | improveCalculation = false 25 | 26 | thread.join() 27 | 28 | println("Main exiting") 29 | } 30 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/util/HttpServiceUrls.scala: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | object HttpServiceUrls { 4 | def randomNumberUpTo(n: Int) = s"https://www.random.org/integers/?num=1&min=1&max=$n&col=1&base=10&format=plain" 5 | } 6 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/util/Utils.scala: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | object Utils { 4 | def time[T](name: String)(operation: => T): T = { 5 | val startTime = System.currentTimeMillis() 6 | 7 | val result = operation 8 | 9 | println(s"$name took ${System.currentTimeMillis - startTime} millis") 10 | 11 | result 12 | } 13 | 14 | def doWork = (1 to 4000000).map(math.pow(2, _)).toList 15 | def doMoreWork = (1 to 4000000).map(math.pow(3, _)).toList 16 | } 17 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes-scala-3/build.sbt: -------------------------------------------------------------------------------- 1 | name := "hello-scala-3" 2 | version := "0.1" 3 | scalaVersion := "3.0.0-RC3" 4 | 5 | // libraryDependencies ++= Seq( 6 | // "org.scalatest" %% "scalatest" % "3.2.5" % Test 7 | // ) 8 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes-scala-3/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.1 2 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes-scala-3/src/main/scala/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | @main def hello = println("Hello, World!") 2 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes-scala-3/src/main/scala/json/JsonDemo.scala: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import JsonSerializable.{ given } 4 | 5 | object JsonDemo extends App { 6 | List(1, 2, 3).toJsonString // [1, 2, 3] 7 | 8 | val ivan = Person("Ivan", "ivan@abv.bg", 23) 9 | val georgi = Person("Georgi", "georgi@gmail.bg", 28) 10 | 11 | ivan.toJson // JsonObject(Map(name -> JsonString(Ivan), email -> JsonString(ivan@abv.bg), age -> JsonNumber(23))) 12 | ivan.toJsonString // {"name": "Ivan", "email": "ivan@abv.bg", "age": 23} 13 | 14 | // Person's JsonSerializable composes with List's JsonSerializable 15 | List(ivan, georgi).toJsonString // [{"name": "Ivan", "email": "ivan@abv.bg", "age": 23}, {"name": "Georgi", "email": "georgi@abv.bg", "age": 28}] 16 | 17 | { 18 | // We might want to skip some fields, like email, for example because we don't want to share 19 | // user's email with other users. 20 | // It's really easy to do so with this implementation - 21 | // just provide another Person's JsonSerializable in this context 22 | 23 | given JsonSerializable[Person] with { 24 | extension (person: Person) def toJson: JsonValue = JsonObject(Map( 25 | "name" -> person.name.toJson, 26 | "age" -> person.age.toJson 27 | )) 28 | } 29 | 30 | // It will compose just as easily 31 | println { 32 | List(ivan, georgi).toJsonString // [{"name": "Ivan", "age": 23}, {"name": "Georgi", "age": 28}] 33 | } 34 | } 35 | 36 | // We can implement this for deserialization, too. circe and play-json are libraries that implements this pattern 37 | // and provide many utilities to ease the creation of (de)serializers. 38 | // Since the mapping depends on only compile-time information the implementation is faster than e.g. Jackson (which uses reflection) 39 | } 40 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes-scala-3/src/main/scala/json/Person.scala: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | case class Person(name: String, email: String, age: Int) 4 | 5 | object Person { 6 | given JsonSerializable[Person] with { 7 | extension (person: Person) def toJson: JsonValue = JsonObject(Map( 8 | "name" -> JsonString(person.name), 9 | "email" -> JsonString(person.email), 10 | "age" -> JsonNumber(person.age) 11 | )) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes-scala-3/src/main/scala/math/Monoid.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | trait Monoid[M] { 4 | extension (a: M) def |+|(b: M): M 5 | def identity: M 6 | } 7 | 8 | object Monoid { 9 | def apply[M](using Monoid[M]) = summon[Monoid[M]] 10 | 11 | given Monoid[Int] with { 12 | extension (a: Int) def |+|(b: Int): Int = a + b 13 | def identity: Int = 0 14 | } 15 | 16 | val intMultiplicativeMonoid = new Monoid[Int] { 17 | extension (a: Int) def |+|(b: Int): Int = a * b 18 | val identity: Int = 1 19 | } 20 | 21 | given Monoid[String] with { 22 | extension (a: String) def |+|(b: String): String = a + b 23 | val identity: String = "" 24 | } 25 | 26 | given [A : Monoid]: Monoid[Option[A]] with { 27 | extension (a: Option[A]) def |+|(b: Option[A]): Option[A] = (a, b) match { 28 | case (Some(n), Some(m)) => Some(n |+| m) 29 | case (Some(_), _) => a 30 | case (_, Some(_)) => b 31 | case _ => None 32 | } 33 | 34 | def identity: Option[A] = None 35 | } 36 | 37 | given [A : Monoid, B : Monoid]: Monoid[(A, B)] with { 38 | extension (a: (A, B)) def |+|(b: (A, B)): (A, B) = (a, b) match { 39 | case ((a1, a2), (b1, b2)) => (a1 |+| b1, a2 |+| b2) 40 | } 41 | 42 | def identity: (A, B) = (Monoid[A].identity, Monoid[B].identity) 43 | } 44 | 45 | given [K, V : Monoid]: Monoid[Map[K, V]] with { 46 | extension (a: Map[K, V]) def |+|(b: Map[K, V]): Map[K, V] = { 47 | val vIdentity = Monoid[V].identity 48 | 49 | (a.keySet ++ b.keySet).foldLeft(identity) { (acc, key) => 50 | acc + (key -> (a.getOrElse(key, vIdentity) |+| b.getOrElse(key, vIdentity))) 51 | } 52 | } 53 | 54 | def identity: Map[K, V] = Map.empty[K, V] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes-scala-3/src/main/scala/math/MonoidDemo.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import Monoid.given 4 | // Or specify just the givens you are going to use: 5 | // import Monoid.{ given Monoid[Int], given Monoid[(_, _)] } 6 | 7 | object MonoidDemo extends App { 8 | def sum[A](xs: List[A])(using Monoid[A]) = { 9 | xs.fold(Monoid[A].identity)(_ |+| _) 10 | } 11 | 12 | sum(List(Rational(3, 4), Rational(5), Rational(7, 4), Rational(11, 13))) 13 | sum(List(Rational(1, 2), Rational(4))) 14 | sum(List(1, 2, 3, 4, 5)) 15 | sum(List(1, 2, 3, 4, 5))(using Monoid.intMultiplicativeMonoid) 16 | 17 | println { 18 | given Monoid[Int] = Monoid.intMultiplicativeMonoid 19 | 20 | sum(List(1, 2, 3, 4, 5)) 21 | } 22 | 23 | sum(List( 24 | Some(Rational(1)), 25 | None, 26 | Some(Rational(1, 2)), 27 | Some(Rational(3, 8)), 28 | None 29 | )) 30 | 31 | (1, 2) |+| (3, 4) 32 | } 33 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes-scala-3/src/main/scala/math/Rational.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import scala.annotation.tailrec 4 | import scala.language.implicitConversions 5 | 6 | class Rational(n: Int, d: Int = 1) extends Ordered[Rational] { 7 | require(d != 0) 8 | 9 | val (numer, denom) = { 10 | val div = gcd(n.abs, d.abs) 11 | 12 | (d.sign * n / div, d.abs / div) 13 | } 14 | 15 | @tailrec 16 | private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) 17 | 18 | def unary_- = new Rational(-numer, denom) 19 | def inverse = new Rational(denom, numer) 20 | 21 | def +(that: Rational) = new Rational( 22 | numer * that.denom + that.numer * denom, 23 | denom * that.denom 24 | ) 25 | def -(that: Rational) = this + (-that) 26 | def *(that: Rational) = new Rational( 27 | numer * that.numer, 28 | denom * that.denom 29 | ) 30 | def /(that: Rational) = this * that.inverse 31 | 32 | override def equals(other: Any): Boolean = other match { 33 | case that: Rational => numer == that.numer && denom == that.denom 34 | case _ => false 35 | } 36 | override def hashCode(): Int = (numer, denom).## 37 | 38 | override def toString: String = s"$numer/$denom" 39 | 40 | override def compare(that: Rational): Int = (this - that).numer 41 | } 42 | 43 | object Rational { 44 | def apply(n: Int, d: Int = 1) = new Rational(n, d) 45 | 46 | implicit def intToRational(n: Int): Rational = new Rational(n) 47 | 48 | given Monoid[Rational] with { 49 | extension (a: Rational) def |+|(b: Rational): Rational = a + b 50 | val identity: Rational = 0 51 | } 52 | } -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/build.sbt: -------------------------------------------------------------------------------- 1 | name := "type-classes" 2 | version := "0.1" 3 | 4 | scalaVersion := "2.13.5" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.2", 8 | "org.scala-lang" % "scala-reflect" % "2.13.5", 9 | "org.typelevel" %% "cats-core" % "2.6.0", 10 | "org.typelevel" %% "spire" % "0.17.0", 11 | "org.scalatest" %% "scalatest" % "3.2.7" % Test 12 | ) 13 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/other/monoid.hs: -------------------------------------------------------------------------------- 1 | class MMonoid a where 2 | identity :: a 3 | op :: a -> a -> a 4 | mmconcat :: [a] -> a 5 | mmconcat = foldr op identity 6 | 7 | newtype SSum a = SSum { getSSum :: a } 8 | deriving (Eq, Ord, Show) 9 | 10 | instance Num a => MMonoid (SSum a) where 11 | identity = SSum 0 12 | SSum x `op` SSum y = SSum (x + y) 13 | 14 | -- getSSum $ (SSum 5 `op` SSum 8 `op` identity)bn 15 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/other/monoid.rs: -------------------------------------------------------------------------------- 1 | trait Monoid { 2 | fn identity() -> Self; 3 | fn op(&self, subject: Self) -> Self; 4 | } 5 | 6 | impl Monoid for i32 { 7 | fn identity() -> i32 { 0 } 8 | fn op(&self, b: i32) -> i32 { *self + b } 9 | } 10 | 11 | fn main() { 12 | println!("{}", i32::identity()); 13 | println!("{}", 1.op(2).to_string()); 14 | } 15 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/other/shapes.rs: -------------------------------------------------------------------------------- 1 | const PI: f64 = 3.1416; 2 | 3 | struct Circle { 4 | radius: f64 5 | } 6 | 7 | struct Rectangle { 8 | height: f64, 9 | width: f64 10 | } 11 | 12 | trait Shape { 13 | fn area(&self) -> f64; 14 | } 15 | 16 | impl Shape for Circle { 17 | fn area(&self) -> f64 { 18 | PI * self.radius * self.radius 19 | } 20 | } 21 | 22 | impl Shape for Rectangle { 23 | fn area(&self) -> f64 { 24 | self.height * self.width 25 | } 26 | } 27 | 28 | fn print_area (shape: &T) { 29 | println!("{}", shape.area()); 30 | } 31 | 32 | fn main() { 33 | let circle = Circle{radius: 2.0}; 34 | let rectangle = Rectangle{height: 3.0, width: 5.0}; 35 | 36 | print_area(&circle); // 12.5664 37 | print_area(&rectangle); // 15 38 | 39 | print_area_generic(&circle); // 12.5664 40 | print_area_generic(&rectangle); // 15 41 | } 42 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.1 2 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/cats/CatsMonoidDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import math.Rational 4 | 5 | import cats.instances.all._ 6 | // We are importing only the syntax we are going to use 7 | import cats.syntax.monoid._ 8 | 9 | object CatsMonoidDemo extends App { 10 | (2, 3) |+| (4, 5) 11 | 12 | implicit val rationalMonoid = new Monoid[Rational] { 13 | def empty: Rational = 0 14 | def combine(x: Rational, y: Rational): Rational = x + y 15 | } 16 | 17 | val map1 = Map(1 -> (2, Rational(3, 2)), 2 -> (3, Rational(4))) 18 | val map2 = Map(2 -> (5, Rational(6)), 3 -> (7, Rational(8, 3))) 19 | 20 | println(map1 |+| map2) 21 | } 22 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/cats/EqDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import math.Rational 4 | import cats.instances.all._ 5 | import cats.syntax.eq._ 6 | 7 | object EqDemo extends App { 8 | //compiles 9 | "" == 2 10 | 11 | // won't compile, uses the Eq typeclass 12 | // "" === 2 13 | 14 | // compiles 15 | 0 === 2 16 | 17 | implicit val rationalEq = new Eq[Rational] { 18 | def eqv(x: Rational, y: Rational): Boolean = x == y 19 | } 20 | 21 | println(Rational(5) === Rational(10, 2)) 22 | println(Rational(5, 2) =!= Rational(10, 2)) 23 | // doesn't compile 24 | // println(Rational(5, 2) === "") 25 | println(Rational(5, 2) === 2) 26 | 27 | case class Box[+A](a: A) { 28 | def contains[B >: A : Eq](b: B) = b === a 29 | } 30 | 31 | Box(1).contains(1) 32 | // doesn't compile 33 | // Box(1).contains("") 34 | 35 | // compiles as it doesn't use the type class 36 | List(1).contains("") 37 | } 38 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/json/JsonDemo.scala: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import json.JsonSerializable.ops._ 4 | 5 | object JsonDemo extends App { 6 | List(1, 2, 3).toJsonString // [1, 2, 3] 7 | 8 | val ivan = Person("Ivan", "ivan@abv.bg", 23) 9 | val georgi = Person("Georgi", "georgi@gmail.bg", 28) 10 | 11 | ivan.toJson // JsonObject(Map(name -> JsonString(Ivan), email -> JsonString(ivan@abv.bg), age -> JsonNumber(23))) 12 | ivan.toJsonString // {"name": "Ivan", "email": "ivan@abv.bg", "age": 23} 13 | 14 | // Person's JsonSerializable composes with List's JsonSerializable 15 | List(ivan, georgi).toJsonString // [{"name": "Ivan", "email": "ivan@abv.bg", "age": 23}, {"name": "Georgi", "email": "georgi@abv.bg", "age": 28}] 16 | 17 | { 18 | // We might want to skip some fields, like email, for example because we don't want to share 19 | // user's email with other users. 20 | // It's really easy to do so with this implementation - 21 | // just provide another Person's JsonSerializable in this context 22 | 23 | implicit val personSerializable = new JsonSerializable[Person] { 24 | // An utility can be created to even more easily create JsonObject 25 | def toJson(person: Person): JsonValue = JsonObject(Map( 26 | "name" -> person.name.toJson, 27 | "age" -> person.age.toJson 28 | )) 29 | } 30 | 31 | // It will compose just as easily 32 | println { 33 | List(ivan, georgi).toJsonString // [{"name": "Ivan", "age": 23}, {"name": "Georgi", "age": 28}] 34 | } 35 | } 36 | 37 | // We can implement this for deserialization, too. circe and play-json are libraries that implements this pattern 38 | // and provide many utilities to ease the creation of (de)serializers. 39 | // Since the mapping depends on only compile-time information the implementation is faster than e.g. Jackson (which uses reflection) 40 | } 41 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/json/Person.scala: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import JsonSerializable.ops._ 4 | 5 | case class Person(name: String, email: String, age: Int) 6 | 7 | object Person { 8 | implicit val personSerializable = new JsonSerializable[Person] { 9 | def toJson(person: Person): JsonValue = JsonObject(Map( 10 | "name" -> person.name.toJson, 11 | "email" -> person.email.toJson, 12 | "age" -> person.age.toJson 13 | )) 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/math/ListOrderingDemo.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import scala.annotation.tailrec 4 | 5 | object ListOrderingDemo extends App { 6 | implicit def listOrdering[A: Ordering]: Ordering[List[A]] = (x: List[A], y: List[A]) => { 7 | val aOrdering = Ordering[A] 8 | import aOrdering.mkOrderingOps 9 | 10 | @tailrec 11 | def loop(x: List[A], y: List[A]): Int = { 12 | if (x.isEmpty && y.isEmpty) 0 13 | else if (x.isEmpty) -1 14 | else if (y.isEmpty) 1 15 | else if (x.head < y.head) -1 16 | else if (y.head < x.head) 1 17 | else loop(x.tail, y.tail) 18 | } 19 | 20 | loop(x, y) 21 | } 22 | 23 | val sortedList = List( 24 | List(1, 2, 3), 25 | List(1, 3, 2), 26 | List(3, 4), 27 | List.empty[Int], 28 | List(1, 1) 29 | ).sorted 30 | 31 | println(sortedList) 32 | } 33 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/math/MonoidDemo.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import Monoid.ops._ 4 | 5 | object MonoidDemo extends App { 6 | def sum[A : Monoid](xs: List[A]) = { 7 | xs.foldLeft(Monoid[A].identity)(_ |+| _) 8 | } 9 | 10 | sum(List(Rational(3, 4), Rational(5), Rational(7, 4), Rational(11, 13))) // 217/26 11 | sum(List(Rational(1, 2), Rational(4))) // 6/8 12 | sum(List(1, 2, 3, 4, 5)) // 15 13 | 14 | // We can explicitly use a different Monoid for a single call 15 | sum(List(1, 2, 3, 4, 5))(Monoid.intMultiplicativeMonoid) // 15 16 | 17 | { 18 | // An we can also explicitly change the context (with a new implicit val or with an import of an implicit val) 19 | 20 | implicit val ratMonoid = Rational.rationalMultiplicativeMonoid 21 | implicit val intMonoid = Monoid.intMultiplicativeMonoid 22 | 23 | sum(List(Rational(3, 4), Rational(5), Rational(7, 4), Rational(11, 13))) // 1155/208 24 | sum(List(Rational(1, 2), Rational(4))) // 2/1 25 | sum(List(1, 2, 3, 4, 5)) // 120 26 | } 27 | 28 | 29 | // Composite monoids: 30 | 31 | // Option: 32 | 33 | sum(List( 34 | Some(Rational(1)), 35 | None, 36 | Some(Rational(1, 2)), 37 | Some(Rational(3, 8)), 38 | None 39 | )) // Some(15/8) 40 | 41 | // Pairs 42 | (2, 3) |+| (4, 5) // (6, 8) 43 | 44 | val map1 = Map(1 -> (2, Rational(3, 2)), 2 -> (3, Rational(4))) 45 | val map2 = Map(2 -> (5, Rational(6)), 3 -> (7, Rational(8, 3))) 46 | 47 | // Composes Monoid[Int] and Monoid[Rational] into a pair monoid Monoid[(Int, Rational)] 48 | // and then composes the pair monoid into Monoid[Map[Int, (Int, Rational)] 49 | println(map1 |+| map2) // Map(1 -> (2,3/2), 2 -> (8,10/1), 3 -> (7,8/3)) 50 | } 51 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/math/NumericTypeclassDemo.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | object NumericTypeclassDemo extends App { 4 | // Both use Numeric type class 5 | List(1, 2, 3, 4).sum 6 | List(1, 2, 3, 4).product 7 | 8 | // List("a", "b", "cd").sum // does not compile: could not find implicit value for parameter num: Numeric[String] 9 | 10 | implicit val rationalNumeric = new Numeric[Rational] { 11 | def plus(x: Rational, y: Rational): Rational = x + y 12 | def minus(x: Rational, y: Rational): Rational = x - y 13 | def times(x: Rational, y: Rational): Rational = x * y 14 | def negate(x: Rational): Rational = -x 15 | 16 | def fromInt(x: Int): Rational = x 17 | def toInt(x: Rational): Int = x.numer / x.denom 18 | def toLong(x: Rational): Long = x.numer / x.denom 19 | def toFloat(x: Rational): Float = x.numer.toFloat / x.denom 20 | def toDouble(x: Rational): Double = x.numer.toDouble / x.denom 21 | 22 | def compare(x: Rational, y: Rational): Int = x compare y 23 | 24 | def parseString(str: String): Option[Rational] = str.split("/") match { 25 | case Array(nStr, dStr) => for { 26 | n <- Numeric[Int].parseString(nStr) 27 | d <- Numeric[Int].parseString(dStr) 28 | } yield Rational(n, d) 29 | case _ => None 30 | } 31 | } 32 | 33 | List(Rational(3, 4), Rational(1, 2), Rational(2, 5)).sum // 33/20 34 | List(Rational(3, 4), Rational(1, 2), Rational(2, 5)).product // 3/20 35 | } 36 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/math/OrderingTypeClassDemo.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import scala.math.abs 4 | 5 | object OrderingTypeClassDemo extends App { 6 | def quickSort[T](xs: List[T])(implicit m: Ordering[T]): List[T] = { 7 | import m.mkOrderingOps 8 | 9 | xs match { 10 | case Nil => Nil 11 | case x :: rest => 12 | val (before, after) = rest partition { _ < x } 13 | quickSort(before) ++ (x :: quickSort(after)) 14 | } 15 | } 16 | 17 | implicit val intOrdering = Ordering[Int].reverse 18 | 19 | quickSort(List(5, 1, 2, 3)) // List(5, 3, 2, 1) 20 | quickSort(List(-5, 1, 2, -2, 3)) // List(3, 2, 1, -2, -5) 21 | quickSort(List(-5, 1, 2, -2, 3))(Ordering.by(abs)) // List(-5, 3, 2, -2, 1) 22 | } 23 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/math/Rational.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import scala.annotation.tailrec 4 | import scala.language.implicitConversions 5 | 6 | class Rational(n: Int, d: Int = 1) extends Ordered[Rational] { 7 | require(d != 0) 8 | 9 | val (numer, denom) = { 10 | val div = gcd(n.abs, d.abs) 11 | 12 | (d.sign * n / div, d.abs / div) 13 | } 14 | 15 | @tailrec 16 | private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) 17 | 18 | def unary_- = new Rational(-numer, denom) 19 | def inverse = new Rational(denom, numer) 20 | 21 | def +(that: Rational) = new Rational( 22 | numer * that.denom + that.numer * denom, 23 | denom * that.denom 24 | ) 25 | def -(that: Rational) = this + (-that) 26 | def *(that: Rational) = new Rational( 27 | numer * that.numer, 28 | denom * that.denom 29 | ) 30 | def /(that: Rational) = this * that.inverse 31 | 32 | override def equals(other: Any): Boolean = other match { 33 | case that: Rational => numer == that.numer && denom == that.denom 34 | case _ => false 35 | } 36 | override def hashCode(): Int = (numer, denom).## 37 | 38 | override def toString: String = s"$numer/$denom" 39 | 40 | override def compare(that: Rational): Int = (this - that).numer 41 | } 42 | 43 | object Rational { 44 | def apply(n: Int, d: Int = 1) = new Rational(n, d) 45 | 46 | implicit def intToRational(n: Int): Rational = new Rational(n) 47 | 48 | implicit val rationalAdditiveMonoid = new Monoid[Rational] { 49 | def op(a: Rational, b: Rational): Rational = a + b 50 | def identity: Rational = 0 51 | } 52 | 53 | val rationalMultiplicativeMonoid = new Monoid[Rational] { 54 | def op(a: Rational, b: Rational): Rational = a * b 55 | def identity: Rational = 1 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/math/Semigroup.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | trait Semigroup[M] { 4 | def op(a: M, b: M): M 5 | } 6 | 7 | object Semigroup { 8 | def apply[A](implicit m: Semigroup[A]): Semigroup[A] = m 9 | 10 | object ops { 11 | implicit class SemigroupOps[A](val a: A)(implicit m: Semigroup[A]) { 12 | def |+|(b: A): A = m.op(a, b) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/parallel/ParallelCollections.scala: -------------------------------------------------------------------------------- 1 | package parallel 2 | 3 | import math.Monoid 4 | import math.Monoid.ops._ 5 | import parallel.Utils.time 6 | 7 | import scala.collection.parallel.CollectionConverters._ 8 | import scala.collection.parallel.ParSeq 9 | 10 | object ParallelCollections extends App { 11 | def sum[A : Monoid](xs: Seq[A]): A = { 12 | xs.fold(Monoid[A].identity)(_ |+| _) 13 | } 14 | 15 | def sum[A : Monoid](xs: ParSeq[A]): A = { 16 | xs.fold(Monoid[A].identity)(_ |+| _) 17 | } 18 | 19 | val seq = 1 to 900000000 20 | 21 | // Non-associative operation 22 | implicit val badMonoid = new Monoid[Int] { 23 | def op(a: Int, b: Int): Int = a - b 24 | def identity: Int = 0 25 | } 26 | 27 | // when using the badMonoid the two will produce different results 28 | println(time("Single threaded")(sum(seq))) 29 | println(time("Multi threaded")(sum(seq.par))) 30 | } 31 | 32 | object Utils { 33 | def time[T](name: String)(operation: => T): T = { 34 | val startTime = System.currentTimeMillis() 35 | 36 | val result = operation 37 | 38 | println(s"$name took ${System.currentTimeMillis - startTime} millis") 39 | 40 | result 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/spire/VectorSpaceDemo.scala: -------------------------------------------------------------------------------- 1 | package spire 2 | 3 | import spire.algebra.Field 4 | import spire.algebra.VectorSpace 5 | import spire.implicits._ 6 | 7 | object VectorSpaceDemo extends App { 8 | val vectorSpaceExpr = 5 *: Vector(1, 2, 3) + (-1) *: Vector(3, 4, -5) 9 | 10 | println(vectorSpaceExpr) 11 | 12 | implicit def fnSpace[A] = new VectorSpace[A => Double, Double] { 13 | implicit def scalar: Field[Double] = Field[Double] 14 | 15 | def zero: A => Double = _ => 0.0 16 | def negate(x: A => Double): A => Double = a => -x(a) 17 | def plus(x: A => Double, y: A => Double): A => Double = a => x(a) + y(a) 18 | def timesl(r: Double, v: A => Double): A => Double = a => r * v(a) 19 | } 20 | 21 | val doubling = (n: Double) => n * 2 22 | val square = (n: Double) => n * n 23 | 24 | val composed = 4 *: doubling - 3.14 *: square 25 | 26 | println { 27 | composed(10) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/types/ClassMetainfoContextDemo.scala: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import math.Rational 4 | 5 | import scala.reflect.ClassTag 6 | import scala.reflect.runtime.universe._ 7 | 8 | object ClassMetainfoContextDemo extends App { 9 | // Does not compile because creation of Java arrays requires metainformation 10 | // (they are different for primitives and the different kind of objects) 11 | // def arrayOf[A](seq: A*) = Array[A](seq: _*) 12 | 13 | // ClassTag gives us the information we need for creating arrays 14 | def arrayOf[A : ClassTag](seq: A*) = Array[A](seq: _*) 15 | 16 | arrayOf(Rational(1), Rational(2)) 17 | 18 | // On JVM no generic type info is available at runtime. 19 | // Scala allows accessing such info through a couple of metainfo type classes like TypeTag, 20 | // which are automatically created for every concrete type 21 | def listElementType[A : TypeTag](xs: List[A]) = { 22 | val t = typeOf[A] 23 | 24 | if (t =:= typeOf[Int]) "List of ints" 25 | else if(t =:= typeOf[Rational]) "List of rationals" 26 | else if (t =:= typeOf[List[String]]) "List of list of strings" 27 | else "List of something else" 28 | } 29 | 30 | val results = List( 31 | listElementType(List(Rational(2), Rational(3))), // List of rationals 32 | listElementType(List(List("a", "b"))), // List of rationals 33 | listElementType(List(List(1, 2))) // List of rationals 34 | ) 35 | 36 | println(results) 37 | } 38 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/build.sbt: -------------------------------------------------------------------------------- 1 | name := "monads-and-applicatives" 2 | version := "0.1" 3 | 4 | scalaVersion := "2.13.5" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.typelevel" %% "cats-core" % "2.3.1" 8 | ) 9 | 10 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.1 2 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/effects/Functor.scala: -------------------------------------------------------------------------------- 1 | package effects 2 | 3 | 4 | trait Functor[F[_]] { 5 | def map[A, B](fa: F[A])(f: A => B): F[B] 6 | 7 | /* 8 | A different view for functors 9 | */ 10 | def lift[A, B](f: A => B): F[A] => F[B] = 11 | fa => map(fa)(f) 12 | 13 | /* 14 | Taken from the red book 15 | */ 16 | def unzip[A,B](fab: F[(A, B)]): (F[A], F[B]) = 17 | (map(fab)(_._1), map(fab)(_._2)) 18 | 19 | def codistribute[A,B](e: Either[F[A], F[B]]): F[Either[A, B]] = 20 | e match { 21 | case Left(fa) => map(fa)(Left(_)) 22 | case Right(fb) => map(fb)(Right(_)) 23 | } 24 | } 25 | 26 | object Functor { 27 | def apply[F[_]](implicit f: Functor[F]) = f 28 | 29 | trait FunctorOps { 30 | implicit class functorOps[F[_] : Functor, A](fa: F[A]) { 31 | def map[B](f: A => B): F[B] = Functor[F].map(fa)(f) 32 | } 33 | } 34 | object ops extends FunctorOps 35 | 36 | // Example implementation for Option 37 | implicit val optionFunctor: Functor[Option] = new Functor[Option] { 38 | def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa match { 39 | case None => None 40 | case Some(a) => Some(f(a)) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/effects/cats/FunctorCompositionDemo.scala: -------------------------------------------------------------------------------- 1 | package effects.cats 2 | 3 | import cats.Functor 4 | import cats.data.Nested 5 | import cats.implicits.toFunctorOps 6 | 7 | object FunctorCompositionDemo extends App { 8 | val listOfOptions = List(Some(1), None, Some(2)) 9 | 10 | println { 11 | // composition 12 | Functor[List].compose[Option].map(listOfOptions)(_ + 1) 13 | } 14 | 15 | // Cats utilities: 16 | println { 17 | Nested(listOfOptions).map(_ + 1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/effects/cats/MonadExample.scala: -------------------------------------------------------------------------------- 1 | package effects.cats 2 | 3 | import cats.{Monad, MonadError} 4 | import effects.maybe.{Just, Maybe, Nthng} 5 | 6 | object MonadExample extends App { 7 | implicit val maybeMonad: Monad[Maybe] = new Monad[Maybe] { 8 | override def pure[A](x: A): Maybe[A] = Just(x) 9 | 10 | override def flatMap[A, B](fa: Maybe[A])(f: A => Maybe[B]): Maybe[B] = fa match { 11 | case Just(a) => f(a) 12 | case _ => Nthng 13 | } 14 | 15 | /* 16 | Needed from Cats to do stack safe monadic recursion 17 | more info [here](https://typelevel.org/cats/typeclasses/monad.html#tailrecm) 18 | */ 19 | override def tailRecM[A, B](a: A)(f: A => Maybe[Either[A, B]]): Maybe[B] = f(a) match { 20 | case Nthng => Nthng 21 | case Just(Left(nextA)) => tailRecM(nextA)(f) // continue the recursion 22 | case Just(Right(b)) => Just(b) // recursion done 23 | } 24 | } 25 | 26 | import cats.implicits._ 27 | 28 | def f(n: Int): Maybe[Int] = Just(n + 1) 29 | def g(n: Int): Maybe[Int] = Just(n * 2) 30 | def h(n: Int): Maybe[Int] = Just(n * n) 31 | 32 | val a = 3 33 | 34 | val result = for { 35 | b <- f(a) 36 | c <- g(b * 2) 37 | d <- h(b + c) 38 | } yield a * b * d 39 | println(result) 40 | } 41 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/effects/id/Id.scala: -------------------------------------------------------------------------------- 1 | package effects.id 2 | 3 | import effects.Monad 4 | 5 | object Id { 6 | 7 | type Id[A] = A 8 | 9 | implicit val idMonad: Monad[Id] = new Monad[Id] { 10 | def unit[A](a: => A): Id[A] = a 11 | def flatMap[A, B](fa: Id[A])(f: A => Id[B]): Id[B] = f(fa) 12 | } 13 | } 14 | 15 | object IdDemo extends App { 16 | 17 | import effects.Monad.ops._ 18 | import Id._ 19 | 20 | val r = for { 21 | a <- "Hello, " 22 | b <- "World!" 23 | } yield a + b 24 | 25 | val a = "Hello, " 26 | val b = "World!" 27 | val r2 = a + b 28 | 29 | Monad[Id].map2(a, b)(_ + _) 30 | a + b 31 | 32 | println(r) 33 | } 34 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/effects/maybe/Maybe.scala: -------------------------------------------------------------------------------- 1 | package effects.maybe 2 | 3 | import effects.Monad 4 | import Monad.ops._ 5 | 6 | sealed trait Maybe[+A] 7 | case class Just[+A](a: A) extends Maybe[A] 8 | case object Nthng extends Maybe[Nothing] 9 | 10 | object Maybe { 11 | implicit val maybeMonad = new Monad[Maybe] { 12 | def flatMap[A, B](fa: Maybe[A])(f: A => Maybe[B]): Maybe[B] = fa match { 13 | case Just(a) => f(a) 14 | case _ => Nthng 15 | } 16 | 17 | def unit[A](a: =>A): Maybe[A] = Just(a) 18 | } 19 | } 20 | 21 | object MaybeDemo extends App { 22 | def f(n: Int): Maybe[Int] = Just(n + 1) 23 | def g(n: Int): Maybe[Int] = Just(n * 2) 24 | def h(n: Int): Maybe[Int] = Just(n * n) 25 | 26 | val a = 3 27 | 28 | val result = for { 29 | b <- f(a) 30 | c <- g(b * 2) 31 | d <- h(b + c) 32 | } yield a * b * d 33 | println(result) 34 | 35 | val maybe1: Maybe[Int] = Just(42) 36 | val maybe2: Maybe[Int] = Just(10) 37 | 38 | Monad.map2(maybe1, maybe2)(_ + _) 39 | 40 | val listOfMaybes = List(Just(1), Nthng, Just(2)) 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/effects/state/RNG.scala: -------------------------------------------------------------------------------- 1 | package effects.state 2 | 3 | import effects.Monad 4 | import Monad.ops._ 5 | import effects.state.State._ 6 | 7 | case class RNG(seed: Long) { 8 | def nextInt: (RNG, Int) = { 9 | val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL 10 | val nextRNG = RNG(newSeed) 11 | val n = (newSeed >>> 16).toInt 12 | 13 | (nextRNG, n) 14 | } 15 | } 16 | 17 | object RNG { 18 | val nextInt = State((rng: RNG) => rng.nextInt) 19 | val nextBoolean = nextInt.map(_ > 0) 20 | } 21 | 22 | object RNGDemo extends App { 23 | import RNG._ 24 | 25 | // Without State we have to do this: 26 | val (rng1, next1) = RNG(System.currentTimeMillis).nextInt 27 | val (rng2, next2) = rng1.nextInt 28 | val (rng3, next3) = rng2.nextInt 29 | 30 | println(next1, next2, next3) 31 | 32 | 33 | //With state 34 | val randomTuple = for { 35 | a <- nextInt 36 | b <- nextInt 37 | c <- nextBoolean 38 | d <- nextBoolean 39 | } yield { 40 | (a, b, a + b, c) 41 | } 42 | 43 | println(randomTuple.run(RNG(System.currentTimeMillis))) 44 | 45 | val b = nextInt.flatMap(i => nextBoolean) 46 | 47 | } 48 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/effects/state/State.scala: -------------------------------------------------------------------------------- 1 | package effects.state 2 | 3 | import effects.Monad 4 | 5 | case class State[S, A](run: S => (S, A)) 6 | 7 | object State { 8 | implicit def stateMonad[S] = { 9 | new Monad[({type StateS[A] = State[S, A]})#StateS] { 10 | def flatMap[A, B](fa: State[S, A])(f: A => State[S, B]): State[S, B] = State { s1 => 11 | val (s2, a) = fa.run(s1) 12 | f(a).run(s2) 13 | } 14 | 15 | def unit[A](a: => A): State[S, A] = State(s => (s, a)) 16 | } 17 | } 18 | 19 | 20 | // TODO: show type lambdas in Scala 3 21 | } 22 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/validation/DomainValidation.scala: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | sealed trait DomainValidation { 4 | def errorMessage: String 5 | } 6 | 7 | case object UsernameHasSpecialCharacters extends DomainValidation { 8 | def errorMessage: String = "Username cannot contain special characters." 9 | } 10 | 11 | case object PasswordDoesNotMeetCriteria extends DomainValidation { 12 | def errorMessage: String = "Password must be at least 10 characters long, including an uppercase and a lowercase letter, one number and one special character." 13 | } 14 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/validation/FormValidation.scala: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | sealed trait FormValidator { 4 | def validateUserName(userName: String): Either[DomainValidation, String] = 5 | Either.cond( 6 | userName.matches("^[a-zA-Z0-9]+$"), 7 | userName, 8 | UsernameHasSpecialCharacters 9 | ) 10 | 11 | def validatePassword(password: String): Either[DomainValidation, String] = 12 | Either.cond( 13 | password.matches("(?=^.{10,}$)((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$"), 14 | password, 15 | PasswordDoesNotMeetCriteria 16 | ) 17 | 18 | def validateForm(username: String, password: String): Either[DomainValidation, RegistrationData] = { 19 | for { 20 | validatedUserName <- validateUserName(username) 21 | validatedPassword <- validatePassword(password) 22 | } yield RegistrationData(validatedUserName, validatedPassword) 23 | } 24 | 25 | } 26 | 27 | object FormValidator extends FormValidator 28 | 29 | object FormValidationDemo extends App { 30 | println{ 31 | FormValidator.validateForm( 32 | username = "fake#$rname", 33 | password = "password" 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/validation/FormValidatorNec.scala: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import effects.{Monad => MyMonad} 4 | 5 | object FormValidatorNec { 6 | import cats.data._ 7 | import cats.data.Validated._ 8 | import cats.implicits._ 9 | 10 | type ValidationResult[A] = ValidatedNec[DomainValidation, A] 11 | 12 | private def validateUserName(userName: String): ValidationResult[String] = 13 | if (userName.matches("^[a-zA-Z0-9]+$")) userName.validNec else UsernameHasSpecialCharacters.invalidNec 14 | 15 | private def validatePassword(password: String): ValidationResult[String] = 16 | if (password.matches("(?=^.{10,}$)((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$")) password.validNec 17 | else PasswordDoesNotMeetCriteria.invalidNec 18 | 19 | 20 | implicit val validatedMonad = new MyMonad[ValidationResult] { 21 | def flatMap[A, B](fa: ValidationResult[A])(f: A => ValidationResult[B]): ValidationResult[B] = fa match { 22 | case Valid(a) => f(a) 23 | case Invalid(_) => fa.asInstanceOf[ValidationResult[B]] 24 | } 25 | 26 | def unit[A](a: => A): ValidationResult[A] = Valid(a) 27 | } 28 | 29 | def validateForm(username: String, password: String): ValidationResult[RegistrationData] = { 30 | validatedMonad.map2( 31 | validateUserName(username), 32 | validatePassword(password) 33 | )(RegistrationData.apply) 34 | } 35 | } 36 | 37 | object FormValidatorNecDemo extends App { 38 | println { 39 | FormValidatorNec.validateForm( 40 | username = "fake$Us#rname", 41 | password = "password" 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/validation/FormValidatorNecApplicative.scala: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import cats.data.Validated.{Invalid, Valid} 4 | import effects.Applicative 5 | import validation.FormValidatorNecApplicative.ValidationResult 6 | 7 | object FormValidatorNecApplicative { 8 | import cats.data._ 9 | import cats.data.Validated._ 10 | import cats.implicits._ 11 | 12 | type ValidationResult[A] = ValidatedNec[DomainValidation, A] 13 | 14 | private def validateUserName(userName: String): ValidationResult[String] = 15 | if (userName.matches("^[a-zA-Z0-9]+$")) userName.validNec else UsernameHasSpecialCharacters.invalidNec 16 | 17 | private def validatePassword(password: String): ValidationResult[String] = 18 | if (password.matches("(?=^.{10,}$)((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$")) password.validNec 19 | else PasswordDoesNotMeetCriteria.invalidNec 20 | 21 | def validateForm(username: String, password: String)(implicit validatedApplicative: Applicative[ValidationResult]): ValidationResult[RegistrationData] = { 22 | validatedApplicative.map2( 23 | validateUserName(username), 24 | validatePassword(password) 25 | )(RegistrationData.apply) 26 | } 27 | } 28 | 29 | object FormValidatorNecApplicativeDemo extends App { 30 | implicit val validatedApplicative: Applicative[ValidationResult] = new Applicative[ValidationResult] { 31 | def map2[A, B, C](fa: ValidationResult[A], fb: ValidationResult[B])(f: (A, B) => C): ValidationResult[C] = 32 | (fa, fb) match { 33 | case (Valid(a), Valid(b)) => Valid(f(a, b)) 34 | case (Valid(_), Invalid(nec)) => Invalid(nec) 35 | case (Invalid(nec), Valid(_)) => Invalid(nec) 36 | case (Invalid(nec1), Invalid(nec2)) => Invalid(nec1 ++ nec2) 37 | } 38 | 39 | def unit[A](a: => A): ValidationResult[A] = Valid(a) 40 | } 41 | 42 | println{ 43 | FormValidatorNecApplicative.validateForm( 44 | username = "fake$Us#rname", 45 | password = "password" 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/validation/RegistrationData.scala: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | final case class RegistrationData(username: String, password: String) 4 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/build.sbt: -------------------------------------------------------------------------------- 1 | name := "cats-and-cats-effects" 2 | version := "0.1" 3 | 4 | scalaVersion := "2.13.6" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.typelevel" %% "cats-core" % "2.6.0", 8 | "org.typelevel" %% "cats-effect" % "3.1.1", 9 | 10 | "org.tpolecat" %% "doobie-core" % "1.0.0-M5", 11 | "org.tpolecat" %% "doobie-hikari" % "1.0.0-M5", 12 | "org.tpolecat" %% "doobie-postgres" % "1.0.0-M5", 13 | 14 | "com.typesafe" % "config" % "1.4.1", 15 | 16 | "co.fs2" %% "fs2-core" % "3.0.4", 17 | "co.fs2" %% "fs2-io" % "3.0.4", 18 | "co.fs2" %% "fs2-reactive-streams" % "3.0.4", 19 | 20 | "org.http4s" %% "http4s-dsl" % "0.23.0-RC1", 21 | "org.http4s" %% "http4s-blaze-server" % "0.23.0-RC1", 22 | "org.http4s" %% "http4s-blaze-client" % "0.23.0-RC1", 23 | "org.http4s" %% "http4s-circe" % "0.23.0-RC1", 24 | 25 | "org.scalatest" %% "scalatest" % "3.2.7" % Test, 26 | "org.typelevel" %% "cats-laws" % "2.6.0" % Test, 27 | "org.typelevel" %% "discipline-scalatest" % "2.1.5" % Test, 28 | "org.typelevel" %% "cats-effect-testing-scalatest" % "1.1.1" % Test 29 | ) 30 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.1 2 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | myApplication { 2 | dVersion = d2 3 | } 4 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/ConcurrentParallelDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import cats.effect.IO 4 | import cats.effect.unsafe.implicits.global 5 | import cats.syntax.apply._ 6 | import cats.syntax.parallel._ 7 | 8 | import scala.concurrent.duration.DurationInt 9 | 10 | object ConcurrentParallelDemo extends App { 11 | // Of course, Monads are also applicatives: 12 | 13 | // // Future 14 | // 15 | // def double(n: Int) = Future { 16 | // Thread.sleep(1000) 17 | // 18 | // n * 2 19 | // } 20 | // 21 | // def square(n: Int) = Future { 22 | // Thread.sleep(1000) 23 | // 24 | // n * n 25 | // } 26 | // 27 | // val calc = for { 28 | // (a, b, c) <- (square(2), square(10), square(20)).tupled 29 | // d <- double(a + b + c) 30 | // } yield d 31 | // 32 | // println(Await.result(calc, 4.seconds)) 33 | 34 | // IO 35 | 36 | def double(n: Int): IO[Int] = IO.sleep(1.second) *> IO(n * 2) 37 | 38 | def square(n: Int): IO[Int] = IO.sleep(1.second) *> IO(n * n) 39 | 40 | val calc = for { 41 | combined <- (square(2), square(10), square(20)).parTupled 42 | (a, b, c) = combined 43 | d <- double(a + b + c) 44 | } yield d 45 | 46 | Utils.time("IO run")(println(calc.unsafeRunSync())) 47 | } 48 | 49 | object Utils { 50 | def time[T](name: String)(operation: => T): T = { 51 | val startTime = System.currentTimeMillis() 52 | 53 | val result = operation 54 | 55 | println(s"$name took ${System.currentTimeMillis - startTime} millis") 56 | 57 | result 58 | } 59 | } -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/DataTypesSyntax.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import cats.syntax.either._ 4 | import cats.syntax.option._ 5 | import cats.syntax.validated._ 6 | 7 | object DataTypesSyntax extends App { 8 | // Option 9 | val maybeOne = 1.some 10 | val maybeN = none[Int] 11 | 12 | val either = maybeOne.toRightNec("It's not there :(") 13 | val validated = maybeOne.toValidNec("It's not there :(") 14 | 15 | val integer = maybeN.orEmpty 16 | 17 | // Validated 18 | 19 | val validatedOne = 1.validNec 20 | val validatedN = "Error".invalidNec 21 | 22 | validatedOne.toEither 23 | 24 | // Either 25 | 26 | val eitherOne = 1.asRight 27 | val eitherN = "Error".asLeft 28 | 29 | val eitherOneChain = 1.rightNec 30 | val eitherNChain = "Error".leftNec 31 | 32 | val recoveredEither = eitherN.recover { 33 | case "Error" => 42.asRight 34 | } 35 | 36 | eitherOneChain.toValidated 37 | } 38 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/EqDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import math.Rational 4 | import cats.instances.all._ 5 | import cats.syntax.eq._ 6 | 7 | object EqDemo extends App { 8 | //compiles 9 | "" == 2 10 | 11 | // won't compile, uses the Eq typeclass 12 | // "" === 2 13 | 14 | // compiles 15 | 0 === 2 16 | 17 | println(Rational(5) === Rational(10, 2)) 18 | println(Rational(5, 2) =!= Rational(10, 2)) 19 | // doesn't compile 20 | // println(Rational(5, 2) === "") 21 | println(Rational(5, 2) === 2) 22 | 23 | case class Box[+A](a: A) { 24 | def contains[B >: A : Eq](b: B) = b === a 25 | } 26 | 27 | Box(1).contains(1) 28 | // doesn't compile 29 | // Box(1).contains("") 30 | 31 | // compiles as it doesn't use the type class 32 | List(1).contains("") 33 | } 34 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/FlatMapMonadMonadErrorDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import cats.syntax.flatMap._ 4 | import cats.syntax.monad._ 5 | import cats.syntax.applicativeError._ 6 | import cats.syntax.monadError._ 7 | import cats.syntax.apply._ 8 | import user.{Email, User} 9 | 10 | import scala.concurrent.{Await, Future} 11 | import scala.concurrent.ExecutionContext.Implicits.global 12 | import scala.concurrent.duration.DurationInt 13 | 14 | object FlatMapMonadMonadErrorDemo extends App { 15 | // FlatMap 16 | 17 | // flatten, flatMap, ... 18 | 19 | def verifyUser(user: User): Future[Boolean] = ??? 20 | def acceptUser(user: User): Future[String] = ??? 21 | def storeUserForReview(user: User): Future[String] = ??? 22 | 23 | def registerUser(user: User): Future[String] = { 24 | verifyUser(user).ifM( 25 | ifTrue = acceptUser(user), 26 | ifFalse = storeUserForReview(user) 27 | ) 28 | } 29 | val user = User("A", Email("a", "gmail.com"), "123") 30 | // registerUser(user) 31 | 32 | // Monad 33 | 34 | def asyncIncrement(n: Int) = Future { 35 | println(s"Contacting our incremental service to increment $n...") 36 | 37 | n + 1 38 | } 39 | 40 | val asyncCalculation = 1.iterateWhileM(asyncIncrement)(_ < 10) 41 | println(Await.result(asyncCalculation, 1.second)) 42 | 43 | // ApplicativeError & MonadError 44 | // They add things like recoverWith, orElse, ... 45 | } 46 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/FoldableDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import cats.syntax.foldable._ 4 | 5 | object FoldableDemo extends App { 6 | def doSomething[F[_] : Foldable, A : Monoid](f: F[A]): A = { 7 | f.forall(_ != Monoid[A].empty) 8 | 9 | f.foldMap(_.toString) 10 | 11 | f.combineAll 12 | } 13 | 14 | doSomething(List(10, 20, 30, 42)) 15 | 16 | } 17 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/FunctorDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import cats.instances.all._ 4 | import cats.syntax.functor._ 5 | 6 | object FunctorDemo extends App { 7 | val ex1 = Option(("a", 0)).swapF 8 | val ex2 = List(("a", 0), ("b", 1), ("c", 2)).swapF.toMap 9 | 10 | def genericDouble[F[_] : Functor](ints: F[Int]): F[Int] = ints.map(_ * 2) 11 | 12 | println(ex1) 13 | println(ex2) 14 | println(genericDouble(Option(10))) 15 | println(genericDouble(List(10, 20, 30))) 16 | } 17 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/MonoidDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import cats.syntax.monoid._ 4 | import cats.instances.all._ 5 | 6 | object MonoidDemo extends App { 7 | 1 |+| 2 8 | "ab".combineN(3) 9 | 10 | 0.isEmpty 11 | 12 | Semigroup[Int].combineAllOption(List(1, 2, 3)) 13 | Monoid[Int].combineAll(List(1, 2, 3)) 14 | } 15 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/ParallelDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import cats.AlternativeUserRegistration._ 4 | import cats.arrow.FunctionK 5 | import cats.data.{EitherNec, ValidatedNec, ZipList} 6 | import cats.instances.all._ 7 | import cats.syntax.apply._ 8 | import cats.syntax.either._ 9 | import cats.syntax.parallel._ 10 | import cats.syntax.validated._ 11 | import user.Email 12 | 13 | object ParallelDemo extends App { 14 | def registerUser(token: String)(name: String, email: String): EitherNec[String, User] = for { 15 | name <- verifyUserToken(token) 16 | user <- ( 17 | validateName(name), 18 | validateEmail(email) 19 | ).parMapN(User.apply) 20 | } yield user 21 | 22 | println { 23 | (List(1, 2, 3), List(10, 20, 30), List(100, 200, 300)).mapN((x, y, z) => x + y + z) 24 | } 25 | println { 26 | (List(1, 2, 3), List(10, 20, 30), List(100, 200, 300)).parMapN((x, y, z) => x + y + z) 27 | } 28 | } 29 | 30 | object AlternativeUserRegistration { 31 | def verifyUserToken(token: String): EitherNec[String, String] = { 32 | if (token.length > 10) token.rightNec 33 | else s"Invalid token: $token".leftNec 34 | } 35 | 36 | def validateName(name: String): EitherNec[String, String] = { 37 | if (name.nonEmpty) name.rightNec 38 | else "Name is empty".leftNec 39 | } 40 | 41 | def validateEmail(email: String): EitherNec[String, Email] = email match { 42 | case Email(user, domain) => Email(user, domain).rightNec 43 | case _ => s"Email is invalid: $email".leftNec 44 | } 45 | } 46 | 47 | case class User(name: String, email: Email) -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/io/Ex01RunningIO.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect.IO 4 | import cats.effect.unsafe.implicits.global 5 | import cats.syntax.parallel._ 6 | 7 | import scala.concurrent.duration.DurationInt 8 | 9 | object Ex01RunningIO { 10 | def double(n: Int): IO[Int] = IO.sleep(1.second) *> IO(n * 2) 11 | 12 | def square(n: Int): IO[Int] = IO.sleep(1.second) *> IO(n * n) 13 | 14 | val calc = for { 15 | combined <- (square(2), square(10), square(20)).parTupled 16 | (a, b, c) = combined 17 | d <- double(a + b + c) 18 | } yield d 19 | 20 | val calcOutput = calc.timed.flatMap(IO.println) 21 | 22 | def main(args: Array[String]): Unit = { 23 | calcOutput.unsafeRunSync() 24 | 25 | // calc.unsafeRunAsync(result => println(result)) 26 | // calc.unsafeToFuture().foreach(println)(global.compute) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/io/Ex02IOApp.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect.{ExitCode, IO, IOApp} 4 | 5 | object Ex02IOApp extends IOApp { 6 | def run(args: List[String]): IO[ExitCode] = 7 | Ex01RunningIO.calcOutput *> IO.pure(ExitCode.Success) 8 | } 9 | 10 | object Ex02IOAppSimple extends IOApp.Simple { 11 | def run: IO[Unit] = Ex01RunningIO.calcOutput 12 | } 13 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/io/Ex03Fibers.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect.{IO, IOApp} 4 | import cats.syntax.parallel._ 5 | import cats.syntax.flatMap._ 6 | 7 | import scala.Function.tupled 8 | 9 | object Ex03Fibers extends IOApp.Simple { 10 | def run: IO[Unit] = { 11 | val fibers = for { 12 | fiber1 <- Ex01RunningIO.double(10).start 13 | fiber2 <- Ex01RunningIO.square(10).start 14 | 15 | a <- fiber1.joinWithNever 16 | b <- fiber2.joinWithNever 17 | } yield a + b 18 | 19 | fibers.timed.flatMap(IO.println) 20 | } 21 | } 22 | 23 | object Ex03FibersBetter extends IOApp.Simple { 24 | def run: IO[Unit] = { 25 | val result = IO.both( 26 | Ex01RunningIO.double(10), 27 | Ex01RunningIO.square(10) 28 | ).map(tupled(_ + _)) 29 | 30 | result.timed.flatMap(IO.println) 31 | } 32 | } 33 | 34 | object Ex03FibersBest extends IOApp.Simple { 35 | def run: IO[Unit] = { 36 | val result = ( 37 | Ex01RunningIO.double(10), 38 | Ex01RunningIO.square(10) 39 | ).parMapN(_ + _) 40 | 41 | // result.timed.flatMap(IO.println) 42 | result.timed >>= IO.println 43 | } 44 | } -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/io/Ex04Cancellation.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect.{IO, IOApp} 4 | import cats.syntax.flatMap._ 5 | import cats.syntax.parallel._ 6 | 7 | import scala.concurrent.duration.{DurationDouble, DurationInt} 8 | 9 | object Ex04Cancellation extends IOApp.Simple { 10 | def run: IO[Unit] = for { 11 | printingFiber <- IO.println("La La La... Hello there :)!").foreverM.onCancel(IO.println("Bye :)")).start 12 | _ <- IO.sleep(2.seconds) 13 | _ <- printingFiber.cancel 14 | } yield () 15 | } 16 | 17 | object Ex04Cancellation2 extends IOApp.Simple { 18 | def run: IO[Unit] = { 19 | val calc1 = IO.sleep(2.second) *> IO.println("Running calc 1") *> IO.pure(42) 20 | val calc2 = IO.sleep(1.second) *> IO.println("Running calc 2") *> IO.pure(421) 21 | 22 | // (calc1, calc2).parMapN(_ + _) >>= IO.println 23 | // ( 24 | // calc1.onCancel(IO.println("calc 1 cancelled")), 25 | // calc2.onCancel(IO.println("calc 2 cancelled")) 26 | // ).parMapN(_ + _) >>= IO.println 27 | // IO.race(calc1.handleErrorWith(_ => IO.never), calc2) >>= IO.println 28 | IO.race(calc1, IO.sleep(1.seconds)) >>= IO.println // run something and cancel it if it does not finish in time 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/io/Ex05Resource.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect.IOApp 4 | import cats.syntax.flatMap._ 5 | 6 | object Ex05Resource extends IOApp.Simple { 7 | import cats.effect.{IO, Resource} 8 | 9 | import java.io._ 10 | 11 | def inputStream(f: File): Resource[IO, FileInputStream] = Resource.fromAutoCloseable(IO(new FileInputStream(f))) 12 | 13 | def outputStream(f: File): Resource[IO, FileOutputStream] = Resource.fromAutoCloseable(IO(new FileOutputStream(f))) 14 | 15 | def inputOutputStreams(in: File, out: File): Resource[IO, (InputStream, OutputStream)] = 16 | for { 17 | inStream <- inputStream(in) 18 | outStream <- outputStream(out) 19 | } yield (inStream, outStream) 20 | 21 | def transmit(origin: InputStream, destination: OutputStream, buffer: Array[Byte], acc: Long): IO[Long] = for { 22 | amount <- IO.blocking(origin.read(buffer, 0, buffer.length)) 23 | count <- 24 | if (amount > -1) 25 | IO.blocking(destination.write(buffer, 0, amount)) >> 26 | transmit(origin, destination, buffer, acc + amount) 27 | else IO.pure(acc) 28 | } yield count 29 | 30 | def transfer(origin: InputStream, destination: OutputStream): IO[Long] = 31 | transmit(origin, destination, new Array[Byte](1024 * 10), 0L) 32 | 33 | def copy(origin: File, destination: File): IO[Long] = 34 | inputOutputStreams(origin, destination).use { case (in, out) => 35 | transfer(in, out) 36 | } 37 | 38 | def run: IO[Unit] = 39 | copy(new File("build.sbt"), new File("build-copy.sbt")) >>= 40 | (c => IO.println(s"Transfered $c bytes")) 41 | } 42 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/io/Ex06SharedConcurrentAccess.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect._ 4 | import cats.effect.implicits._ 5 | import cats.effect.std.Queue 6 | import cats.implicits._ 7 | 8 | class Channel[A](ref: Ref[IO, Int], q: Queue[IO, A]) { 9 | def take: IO[A] = q.take <* ref.update(_ - 1) 10 | 11 | def put(a: A): IO[Unit] = ref.update(_ + 1) >> q.offer(a) 12 | 13 | def append(as: Seq[A]): IO[Unit] = 14 | ref.update(_ + as.length) >> 15 | as.traverse(q.offer).void 16 | 17 | def length: IO[Int] = ref.get 18 | } 19 | 20 | object Channel { 21 | def apply[A](): IO[Channel[A]] = for { 22 | ref <- IO.ref(0) 23 | q <- Queue.unbounded[IO, A] 24 | } yield new Channel(ref, q) 25 | } 26 | 27 | object Ex06SharedConcurrentAccess { 28 | // See tests in ChannelTestSuite 29 | } -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/math/Rational.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import cats.{Eq, Monoid} 4 | 5 | import scala.annotation.tailrec 6 | import scala.language.implicitConversions 7 | 8 | class Rational(n: Int, d: Int = 1) extends Ordered[Rational] { 9 | require(d != 0) 10 | 11 | val (numer, denom) = { 12 | val div = gcd(n.abs, d.abs) 13 | 14 | (d.sign * n / div, d.abs / div) 15 | } 16 | 17 | @tailrec 18 | private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) 19 | 20 | def unary_- = new Rational(-numer, denom) 21 | def inverse = new Rational(denom, numer) 22 | 23 | def +(that: Rational) = new Rational( 24 | numer * that.denom + that.numer * denom, 25 | denom * that.denom 26 | ) 27 | def -(that: Rational) = this + (-that) 28 | def *(that: Rational) = new Rational( 29 | numer * that.numer, 30 | denom * that.denom 31 | ) 32 | def /(that: Rational) = this * that.inverse 33 | 34 | override def equals(other: Any): Boolean = other match { 35 | case that: Rational => numer == that.numer && denom == that.denom 36 | case _ => false 37 | } 38 | override def hashCode(): Int = (numer, denom).## 39 | 40 | override def toString: String = s"$numer/$denom" 41 | 42 | override def compare(that: Rational): Int = (this - that).numer 43 | } 44 | 45 | object Rational { 46 | def apply(n: Int, d: Int = 1) = new Rational(n, d) 47 | 48 | implicit def intToRational(n: Int): Rational = new Rational(n) 49 | 50 | // implicit val rationalEq = new Eq[Rational] { 51 | // def eqv(x: Rational, y: Rational): Boolean = x == y 52 | // } 53 | 54 | implicit val rationalEq: Eq[Rational] = Eq.fromUniversalEquals 55 | 56 | implicit val rationalMonoid: Monoid[Rational] = new Monoid[Rational] { 57 | def empty: Rational = 0 58 | def combine(x: Rational, y: Rational): Rational = x + y 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/user/UserRegistration.scala: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import cats.data.ValidatedNec 4 | import cats.syntax.validated._ 5 | 6 | sealed trait RegistrationFormError 7 | case object NameIsEmpty extends RegistrationFormError 8 | case class InvalidEmail(email: String) extends RegistrationFormError 9 | case object PasswordTooShort extends RegistrationFormError 10 | 11 | case class RegistrationForm( 12 | name: String, 13 | email: String, 14 | password: String, 15 | ) 16 | 17 | case class User( 18 | name: String, 19 | email: Email, 20 | passwordHash: String, 21 | ) 22 | 23 | case class Email(user: String, domain: String) 24 | object Email { 25 | def unapply(email: String): Option[(String, String)] = email.split("@") match { 26 | case Array(user, domain) if user.nonEmpty && domain.nonEmpty => 27 | Some((user, domain)) 28 | case _ => None 29 | } 30 | } 31 | 32 | object UserRegistration { 33 | def validateName(name: String): ValidatedNec[RegistrationFormError, String] = { 34 | if (name.nonEmpty) name.validNec 35 | else NameIsEmpty.invalidNec 36 | } 37 | 38 | def validateEmail(email: String): ValidatedNec[RegistrationFormError, Email] = email match { 39 | case Email(user, domain) => Email(user, domain).validNec 40 | case _ => InvalidEmail(email).invalidNec 41 | } 42 | 43 | def validatePassword(password: String): ValidatedNec[RegistrationFormError, String] = { 44 | if (password.length > 8) password.validNec.map(p => s"hashed $p") 45 | else PasswordTooShort.invalidNec 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/test/scala/io/ChannelTestSuite.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect._ 4 | import cats.effect.testing.scalatest.AsyncIOSpec 5 | import cats.implicits._ 6 | import org.scalatest.freespec.AsyncFreeSpec 7 | import org.scalatest.matchers.should.Matchers 8 | 9 | import scala.concurrent.TimeoutException 10 | import scala.concurrent.duration.DurationInt 11 | 12 | class ChannelTestSuite extends AsyncFreeSpec with AsyncIOSpec with Matchers { 13 | "Single put take passes" in { 14 | val result = for { 15 | chan <- Channel[Int]() 16 | _ <- chan.put(1) 17 | v <- chan.take 18 | } yield v 19 | 20 | result.asserting(_ shouldBe 1) 21 | } 22 | 23 | "Test appending of multiple values" in { 24 | val result = for { 25 | chan <- Channel[Int]() 26 | _ <- chan.append(List.range(0, 25)) 27 | _ <- chan.take.replicateA(15) 28 | len <- chan.length 29 | } yield len 30 | 31 | result.asserting(_ shouldBe 10) 32 | } 33 | 34 | "We gonna block dawg" in { 35 | val result = for { 36 | chan <- Channel[Int]() 37 | _ <- chan.append(List.range(0, 5)) 38 | maybeError <- chan.take 39 | .replicateA(6) 40 | .timeout(5.seconds) 41 | .as(IO.pure(None)) 42 | .handleErrorWith(e => IO.pure(Some(e))) 43 | } yield maybeError 44 | 45 | result.asserting { 46 | case Some(e) => assert(e.isInstanceOf[TimeoutException]) 47 | case None => fail("Wut?") 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/test/scala/math/RationalMonoidTest.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import cats.kernel.laws.discipline.MonoidTests 4 | import org.scalacheck.{Arbitrary, Gen} 5 | import org.scalatest.funsuite.AnyFunSuite 6 | import org.scalatestplus.scalacheck.Checkers 7 | import org.typelevel.discipline.scalatest.FunSuiteDiscipline 8 | 9 | class RationalMonoidTest extends AnyFunSuite with FunSuiteDiscipline with Checkers { 10 | implicit val arbitraryRational: Arbitrary[Rational] = Arbitrary { 11 | for { 12 | a <- Gen.choose(-100, 100) 13 | b <- Gen.choose(-100, 100) 14 | if b != 0 15 | } yield Rational(a, b) 16 | } 17 | 18 | checkAll("Rational Monoid", MonoidTests[Rational].monoid) 19 | } 20 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/build.sbt: -------------------------------------------------------------------------------- 1 | name := "building-a-scala-app" 2 | version := "0.1" 3 | 4 | scalaVersion := "2.13.6" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.typelevel" %% "cats-core" % "2.6.0", 8 | "org.typelevel" %% "cats-effect" % "3.1.1", 9 | 10 | "org.tpolecat" %% "doobie-core" % "1.0.0-M5", 11 | "org.tpolecat" %% "doobie-hikari" % "1.0.0-M5", 12 | "org.tpolecat" %% "doobie-postgres" % "1.0.0-M5", 13 | 14 | "com.typesafe" % "config" % "1.4.1", 15 | 16 | "co.fs2" %% "fs2-core" % "3.0.4", 17 | "co.fs2" %% "fs2-io" % "3.0.4", 18 | "co.fs2" %% "fs2-reactive-streams" % "3.0.4", 19 | 20 | "io.circe" %% "circe-core" % "0.14.1", 21 | "io.circe" %% "circe-generic" % "0.14.1", 22 | "io.circe" %% "circe-parser" % "0.14.1", 23 | 24 | "org.http4s" %% "http4s-dsl" % "0.23.0-RC1", 25 | "org.http4s" %% "http4s-blaze-server" % "0.23.0-RC1", 26 | "org.http4s" %% "http4s-blaze-client" % "0.23.0-RC1", 27 | "org.http4s" %% "http4s-circe" % "0.23.0-RC1", 28 | 29 | "ch.qos.logback" % "logback-classic" % "1.2.3", 30 | 31 | "org.scalatest" %% "scalatest" % "3.2.7" % Test, 32 | "org.typelevel" %% "cats-laws" % "2.6.0" % Test, 33 | "org.typelevel" %% "discipline-scalatest" % "2.1.5" % Test, 34 | "org.typelevel" %% "cats-effect-testing-scalatest" % "1.1.1" % Test 35 | ) 36 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/fahrenheit.txt: -------------------------------------------------------------------------------- 1 | 10 2 | 20 3 | 30 4 | 40 5 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.1 2 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | myApplication { 2 | dVersion = d2 3 | } 4 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | true 9 | 10 | [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/Example1_HttpRoutes.scala: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import cats.data.Kleisli 4 | import cats.effect.{ExitCode, IO, IOApp} 5 | import org.http4s.{HttpApp, HttpRoutes, Request, Response, Status} 6 | import org.http4s.dsl.io._ 7 | import org.http4s.implicits._ 8 | import cats.syntax.semigroupk._ 9 | import org.http4s.blaze.server.BlazeServerBuilder 10 | import org.http4s.server.Router 11 | 12 | import scala.concurrent.ExecutionContext.global 13 | 14 | object Example1_HttpRoutes { 15 | 16 | val mostSimpleRoute = HttpRoutes.of[IO] { 17 | case _ => 18 | IO(Response(Status.Ok)) 19 | } 20 | 21 | val helloRoutes = HttpRoutes.of[IO] { 22 | case GET -> Root / "hello" / name => // GET /hello/zdravko 23 | Ok(s"Hello, $name.") 24 | case GET -> Root / "hola" / name => 25 | Ok(s"¡Hola, $name!") 26 | } 27 | 28 | val combined = helloRoutes <+> mostSimpleRoute 29 | 30 | // Or define them under different path prefixes like that 31 | Router("/" -> helloRoutes, "/simple" -> mostSimpleRoute) 32 | 33 | val httpApp: HttpApp[IO] = helloRoutes.orNotFound 34 | } 35 | 36 | object HelloWorldApp extends IOApp { 37 | def run(args: List[String]): IO[ExitCode] = 38 | BlazeServerBuilder[IO](global) 39 | .bindHttp(8080, "localhost") 40 | .withHttpApp(Example1_HttpRoutes.httpApp) 41 | .resource 42 | .use(_ => IO.never) 43 | .as(ExitCode.Success) 44 | } 45 | 46 | 47 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/Example2_PathParameters.scala: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import cats.effect.IO 4 | import cats.implicits._ 5 | import http.Utils._ 6 | import org.http4s.HttpRoutes 7 | import org.http4s.dsl.io._ 8 | import org.http4s.implicits._ 9 | 10 | import java.time.LocalDate 11 | import scala.util.Try 12 | 13 | object Example2_PathParameters extends App { 14 | 15 | // Match rest of the path 16 | val app = HttpRoutes.of[IO] { 17 | case GET -> "hello" /: rest => 18 | Ok(s"""Rest of the path is: $rest""") 19 | } 20 | 21 | println(requestEntityUnsafe[String](app)(get(uri"hello/rest/of/the/path"))) 22 | 23 | 24 | def getUserName(userId: Int): IO[String] = IO.pure(s"User$userId") 25 | 26 | val usersService = HttpRoutes.of[IO] { 27 | case GET -> Root / "users" / IntVar(userId) => 28 | Ok(getUserName(userId)) 29 | case GET -> Root / "usersLong" / LongVar(_) => 30 | ??? 31 | case GET -> Root / "usersUUID" / UUIDVar(_) => 32 | ??? 33 | } 34 | 35 | println(requestEntityUnsafe[String](usersService)(get(uri"/users/1"))) 36 | println(requestUnsafe(usersService)(get(uri"/users/two"))) 37 | 38 | /** 39 | * Custom extractors 40 | * We need: 41 | * def unapply(str: String): Option[T] 42 | */ 43 | 44 | object LocalDateVar { 45 | def unapply(str: String): Option[LocalDate] = { 46 | if (str.nonEmpty) 47 | Try(LocalDate.parse(str)).toOption 48 | else 49 | None 50 | } 51 | } 52 | 53 | def getTemperatureForecast(date: LocalDate): IO[Double] = IO.println(date) *> IO(42.23) 54 | 55 | val dailyWeatherService = HttpRoutes.of[IO] { 56 | case GET -> Root / "weather" / "temperature" / LocalDateVar(localDate) => 57 | Ok(getTemperatureForecast(localDate).map(s"The temperature on $localDate will be: " + _)) 58 | } 59 | 60 | println(requestEntityUnsafe[String](dailyWeatherService)(get(uri"/weather/temperature/2016-11-05"))) 61 | } 62 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/Example4_Responses.scala: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import cats.data.NonEmptyList 4 | import cats.effect.IO 5 | import cats.effect.unsafe.implicits.global 6 | import org.http4s.CacheDirective.`no-cache` 7 | import org.http4s.dsl.io._ 8 | import org.http4s.headers.{`Cache-Control`, `WWW-Authenticate`} 9 | import org.http4s.{Challenge, HttpDate, ResponseCookie} 10 | 11 | object Example4_Responses { 12 | 13 | // Simple response builders 14 | Ok() 15 | Ok().unsafeRunSync() 16 | NoContent() 17 | 18 | // Response with body 19 | Ok("Ok response.") 20 | Ok("Ok response.", `Cache-Control`(NonEmptyList(`no-cache`(), Nil))).unsafeRunSync() 21 | Unauthorized(`WWW-Authenticate`(Challenge("scheme", "realm", Map.empty)), "Provide xxx") 22 | 23 | Ok(IO.pure("It can also accept IO")).unsafeRunSync() 24 | 25 | // Add cookie util method 26 | Ok("Ok response.").map(_.addCookie(ResponseCookie("foo", "bar"))).unsafeRunSync() 27 | 28 | val cookieResp = for { 29 | resp <- Ok("Ok response.") 30 | now <- HttpDate.current[IO] 31 | } yield resp.addCookie(ResponseCookie("foo", "bar", expires = Some(now), httpOnly = true, secure = true)) 32 | cookieResp.unsafeRunSync().cookies 33 | } 34 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/Utils.scala: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import cats.effect.IO 4 | import cats.effect.unsafe.implicits.global 5 | import org.http4s._ 6 | import org.http4s.implicits._ 7 | 8 | object Utils { 9 | def get(uri: Uri): Request[IO] = Request[IO](Method.GET, uri) 10 | 11 | def requestUnsafe(routes: HttpRoutes[IO])(request: Request[IO]): Response[IO] = { 12 | routes.orNotFound.run(request).unsafeRunSync() 13 | } 14 | def requestEntityUnsafe[T](routes: HttpRoutes[IO])(request: Request[IO])(implicit d: EntityDecoder[IO, T]): T = { 15 | routes.orNotFound.run(request).flatMap(_.as[T]).unsafeRunSync() 16 | } 17 | 18 | def printResponseAndEntity[T](routes: HttpRoutes[IO])(request: Request[IO])(implicit d: EntityDecoder[IO, T]): IO[Response[IO]] = for { 19 | resp <- routes.orNotFound.run(request) 20 | _ <- IO.println(resp) 21 | _ <- IO.println(resp.as[T]) 22 | } yield resp 23 | } 24 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/client/Joke.scala: -------------------------------------------------------------------------------- 1 | package http.client 2 | 3 | import cats.effect.IO 4 | import io.circe.generic.semiauto.deriveCodec 5 | import io.circe.{Codec, Decoder, Encoder} 6 | import org.http4s.circe.{jsonEncoderOf, jsonOf} 7 | import org.http4s.{EntityDecoder, EntityEncoder} 8 | 9 | final case class Joke(joke: String) extends AnyVal 10 | 11 | object Joke { 12 | implicit val jokeCodec: Codec[Joke] = deriveCodec[Joke] 13 | } 14 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/client/JokeRouter.scala: -------------------------------------------------------------------------------- 1 | package http.client 2 | 3 | import cats.effect.IO 4 | import io.circe.Json 5 | import org.http4s.circe.CirceEntityCodec.circeEntityEncoder 6 | import org.http4s.circe.jsonDecoder 7 | import org.http4s.{HttpApp, HttpRoutes} 8 | import org.http4s.dsl.io._ 9 | import org.http4s.implicits._ 10 | 11 | class JokeRouter(jokeService: JokeService) { 12 | def jokeRoutes: HttpRoutes[IO] = HttpRoutes.of[IO] { 13 | case GET -> Root / "joke" => 14 | jokeService.getJoke.flatMap(Ok(_)) 15 | case req => 16 | req.as[Json].flatMap(Ok(_)) 17 | } 18 | 19 | def httpApp: HttpApp[IO] = jokeRoutes.orNotFound 20 | } 21 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/client/JokeService.scala: -------------------------------------------------------------------------------- 1 | package http.client 2 | 3 | import cats.effect.IO 4 | import org.http4s._ 5 | import org.http4s.client.Client 6 | import org.http4s.implicits.http4sLiteralsSyntax 7 | 8 | import org.http4s.circe.CirceEntityCodec.circeEntityDecoder 9 | 10 | final case class JokeError(e: Throwable) extends RuntimeException 11 | 12 | class JokeService(client: Client[IO]) { 13 | def getJoke: IO[Joke] = 14 | client.expect[Joke](Request[IO](Method.GET, uri"https://icanhazdadjoke.com/")) 15 | .handleErrorWith(t => IO.raiseError(JokeError(t))) 16 | } 17 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/client/Main.scala: -------------------------------------------------------------------------------- 1 | package http.client 2 | 3 | import cats.effect.{ExitCode, IO, IOApp} 4 | import org.http4s.blaze.client.BlazeClientBuilder 5 | import org.http4s.blaze.server.BlazeServerBuilder 6 | 7 | import scala.concurrent.ExecutionContext.global 8 | 9 | object Main extends IOApp { 10 | def run(args: List[String]): IO[ExitCode] = { 11 | val appResource = for { 12 | client <- BlazeClientBuilder[IO](global).resource 13 | 14 | jokeService = new JokeService(client) 15 | jokeRouter = new JokeRouter(jokeService) 16 | 17 | server <- BlazeServerBuilder[IO](global) 18 | .bindHttp(8080, "localhost") 19 | .withHttpApp(jokeRouter.httpApp) 20 | .resource 21 | } yield (client, server) 22 | 23 | appResource.use(_ => IO.never) 24 | .as(ExitCode.Success) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/middlewares/SimpleMiddlewareExample.scala: -------------------------------------------------------------------------------- 1 | package http.middlewares 2 | 3 | import cats.data.Kleisli 4 | import cats.effect._ 5 | import org.http4s._ 6 | import org.http4s.dsl.io._ 7 | import org.http4s.implicits._ 8 | 9 | 10 | 11 | object SimpleMiddlewareExample { 12 | def addHeaderMiddleware(service: HttpRoutes[IO], header: Header.ToRaw): HttpRoutes[IO] = Kleisli { (req: Request[IO]) => 13 | service(req).map { 14 | case Status.Successful(resp) => 15 | resp.putHeaders(header) 16 | case resp => 17 | resp 18 | } 19 | } 20 | 21 | val service: HttpRoutes[IO] = HttpRoutes.of[IO] { 22 | case GET -> Root / "bad" => 23 | BadRequest() 24 | case _ => 25 | Ok() 26 | } 27 | 28 | val wrappedService: HttpRoutes[IO] = addHeaderMiddleware(service, "SomeKey" -> "SomeValue") 29 | } 30 | 31 | object SimpleMiddlewareTest extends IOApp { 32 | 33 | import SimpleMiddlewareExample._ 34 | 35 | override def run(args: List[String]): IO[ExitCode] = { 36 | val goodRequest = Request[IO](Method.GET, uri"/") 37 | val badRequest = Request[IO](Method.GET, uri"/bad") 38 | 39 | for { 40 | resp1 <- wrappedService.orNotFound(goodRequest) 41 | _ <- IO.println(resp1) 42 | resp2 <- wrappedService.orNotFound(badRequest) 43 | _ <- IO.println(resp2) 44 | } yield ExitCode.Success 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/middlewares/auth/Auth.scala: -------------------------------------------------------------------------------- 1 | package http.middlewares.auth 2 | 3 | import cats.data.{Kleisli, OptionT} 4 | import cats.effect.IO 5 | import cats.implicits._ 6 | import org.http4s.dsl.io._ 7 | import org.http4s.server.AuthMiddleware 8 | import org.http4s.{AuthedRoutes, Request, Response, ResponseCookie} 9 | 10 | object Auth { 11 | 12 | 13 | def login(userId: Int): IO[Response[IO]] = 14 | Ok("Logged in!").map(_.addCookie(ResponseCookie("authcookie", userId.toString))) 15 | 16 | 17 | def retrieveUser(id: Int) = 18 | UserDatabase(id).map(_.toRight("User not found")) 19 | 20 | val authUser: Kleisli[IO, Request[IO], Either[String, User]] = 21 | Kleisli { request => 22 | val eitherUserId = for { 23 | cookie <- request.cookies.find(_.name == "authcookie").toRight("Cookie parsing error") 24 | userId <- Either.catchOnly[NumberFormatException](cookie.content.toInt).leftMap(_.toString) 25 | } yield userId 26 | 27 | eitherUserId.fold( 28 | error => IO.pure(Left(error)), 29 | retrieveUser 30 | ) 31 | } 32 | 33 | 34 | val onFailure: AuthedRoutes[String, IO] = Kleisli(req => OptionT.liftF(Forbidden(req.context))) 35 | val middleware: AuthMiddleware[IO, User] = AuthMiddleware(authUser, onFailure) 36 | 37 | } 38 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/middlewares/auth/AuthService.scala: -------------------------------------------------------------------------------- 1 | package http.middlewares.auth 2 | 3 | import cats.effect.IO 4 | import org.http4s.dsl.io._ 5 | import org.http4s.implicits._ 6 | import org.http4s.server.Router 7 | import org.http4s.{AuthedRoutes, HttpRoutes} 8 | 9 | object AuthService { 10 | 11 | object UserIdQueryParam extends QueryParamDecoderMatcher[Int]("userId") 12 | 13 | val unprotectedRoutes = HttpRoutes.of[IO] { 14 | case POST -> Root / "login" :? UserIdQueryParam(userId) => 15 | Auth.login(userId) 16 | } 17 | 18 | val authedRoutes: AuthedRoutes[User, IO] = 19 | AuthedRoutes.of { 20 | case GET -> Root / "welcome" as user => 21 | Ok(s"Welcome, ${user.name}") 22 | } 23 | 24 | val httpApp = Router( 25 | "/" -> unprotectedRoutes, 26 | "/protected" -> Auth.middleware(authedRoutes) 27 | ).orNotFound 28 | } 29 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/middlewares/auth/Main.scala: -------------------------------------------------------------------------------- 1 | package http.middlewares.auth 2 | 3 | import cats.effect.{ExitCode, IO, IOApp} 4 | import org.http4s.blaze.server.BlazeServerBuilder 5 | 6 | import scala.concurrent.ExecutionContext.global 7 | 8 | object Main extends IOApp { 9 | 10 | def run(args: List[String]): IO[ExitCode] = 11 | BlazeServerBuilder[IO](global) 12 | .bindHttp(8080, "localhost") 13 | .withHttpApp(AuthService.httpApp) 14 | .resource 15 | .use(_ => IO.never) 16 | .as(ExitCode.Success) 17 | } 18 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/middlewares/auth/User.scala: -------------------------------------------------------------------------------- 1 | package http.middlewares.auth 2 | 3 | case class User(id: Int, name: String) 4 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/middlewares/auth/UserDatabase.scala: -------------------------------------------------------------------------------- 1 | package http.middlewares.auth 2 | 3 | import cats.effect.IO 4 | 5 | object UserDatabase { 6 | val users = Map( 7 | 1 -> User(1, "Viktor"), 8 | 2 -> User(2, "Zdravko"), 9 | 3 -> User(3, "Boyan") 10 | ) 11 | 12 | def apply(id: Int): IO[Option[User]] = IO.pure(users.get(id)) 13 | } 14 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/middlewares/gzip/GZipMiddlewareExample.scala: -------------------------------------------------------------------------------- 1 | package http.middlewares.gzip 2 | 3 | import cats.effect._ 4 | import org.http4s._ 5 | import org.http4s.dsl.io._ 6 | import org.http4s.headers.`Accept-Encoding` 7 | import org.http4s.implicits._ 8 | import org.http4s.server.middleware._ 9 | 10 | 11 | object GZipMiddlewareExample extends IOApp { 12 | val service: HttpRoutes[IO] = HttpRoutes.of[IO] { 13 | case _ => 14 | Ok("I repeat myself when I'm under stress. " * 3) 15 | } 16 | 17 | val zipService = GZip(service) 18 | 19 | override def run(args: List[String]): IO[ExitCode] = { 20 | val request = Request[IO](Method.GET, uri"/") 21 | 22 | val acceptHeader = `Accept-Encoding`(ContentCoding.gzip) 23 | val acceptGZipRequest = request.putHeaders(acceptHeader) 24 | 25 | for { 26 | resp1 <- zipService.orNotFound(request) 27 | body1 <- resp1.as[String] 28 | _ <- IO.println(resp1) 29 | _ <- IO.println(body1) 30 | resp2 <- zipService.orNotFound(acceptGZipRequest) 31 | body2 <- resp2.as[String] 32 | _ <- IO.println(resp2) 33 | _ <- IO.println(body2) 34 | } yield ExitCode.Success 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/http/tweets/Tweet.scala: -------------------------------------------------------------------------------- 1 | package http.tweets 2 | 3 | import cats.effect.IO 4 | import io.circe.Codec 5 | import org.http4s.{EntityDecoder, EntityEncoder} 6 | 7 | 8 | case class Tweet(id: Int, content: String, likes: Int = 0) 9 | 10 | object Tweet { 11 | import io.circe.generic.semiauto._ 12 | import org.http4s.circe._ 13 | 14 | implicit val tweetCodec: Codec[Tweet] = deriveCodec 15 | 16 | implicit def tweetEntityEncoder: EntityEncoder[IO, Tweet] = jsonEncoderOf[IO, Tweet] 17 | implicit def tweetsEntityEncoder: EntityEncoder[IO, Seq[Tweet]] = jsonEncoderOf[IO, Seq[Tweet]] 18 | implicit def tweetEntityDecoder: EntityDecoder[IO, Tweet] = jsonOf[IO, Tweet] 19 | } 20 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/io/Ex01RunningIO.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect.IO 4 | import cats.effect.unsafe.implicits.global 5 | import cats.syntax.parallel._ 6 | 7 | import scala.concurrent.duration.DurationInt 8 | 9 | object Ex01RunningIO { 10 | def double(n: Int): IO[Int] = IO.sleep(1.second) *> IO(n * 2) 11 | 12 | def square(n: Int): IO[Int] = IO.sleep(1.second) *> IO(n * n) 13 | 14 | val calc = for { 15 | combined <- (square(2), square(10), square(20)).parTupled 16 | (a, b, c) = combined 17 | d <- double(a + b + c) 18 | } yield d 19 | 20 | val calcOutput = calc.timed.flatMap(IO.println) 21 | 22 | def main(args: Array[String]): Unit = { 23 | calcOutput.unsafeRunSync() 24 | 25 | // calc.unsafeRunAsync(result => println(result)) 26 | // calc.unsafeToFuture().foreach(println)(global.compute) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/io/Ex02IOApp.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect.{ExitCode, IO, IOApp} 4 | 5 | object Ex02IOApp extends IOApp { 6 | def run(args: List[String]): IO[ExitCode] = 7 | Ex01RunningIO.calcOutput *> IO.pure(ExitCode.Success) 8 | } 9 | 10 | object Ex02IOAppSimple extends IOApp.Simple { 11 | def run: IO[Unit] = Ex01RunningIO.calcOutput 12 | } 13 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/io/Ex03Fibers.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect.{IO, IOApp} 4 | import cats.syntax.parallel._ 5 | import cats.syntax.flatMap._ 6 | 7 | import scala.Function.tupled 8 | 9 | object Ex03Fibers extends IOApp.Simple { 10 | def run: IO[Unit] = { 11 | val fibers = for { 12 | fiber1 <- Ex01RunningIO.double(10).start 13 | fiber2 <- Ex01RunningIO.square(10).start 14 | 15 | a <- fiber1.joinWithNever 16 | b <- fiber2.joinWithNever 17 | } yield a + b 18 | 19 | fibers.timed.flatMap(IO.println) 20 | } 21 | } 22 | 23 | object Ex03FibersBetter extends IOApp.Simple { 24 | def run: IO[Unit] = { 25 | val result = IO.both( 26 | Ex01RunningIO.double(10), 27 | Ex01RunningIO.square(10) 28 | ).map(tupled(_ + _)) 29 | 30 | result.timed.flatMap(IO.println) 31 | } 32 | } 33 | 34 | object Ex03FibersBest extends IOApp.Simple { 35 | def run: IO[Unit] = { 36 | val result = ( 37 | Ex01RunningIO.double(10), 38 | Ex01RunningIO.square(10) 39 | ).parMapN(_ + _) 40 | 41 | // result.timed.flatMap(IO.println) 42 | result.timed >>= IO.println 43 | } 44 | } -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/io/Ex04Cancellation.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect.{IO, IOApp} 4 | import cats.syntax.flatMap._ 5 | import cats.syntax.parallel._ 6 | 7 | import scala.concurrent.duration.{DurationDouble, DurationInt} 8 | 9 | object Ex04Cancellation extends IOApp.Simple { 10 | def run: IO[Unit] = for { 11 | printingFiber <- IO.println("La La La... Hello there :)!").foreverM.onCancel(IO.println("Bye :)")).start 12 | _ <- IO.sleep(2.seconds) 13 | _ <- printingFiber.cancel 14 | } yield () 15 | } 16 | 17 | object Ex04Cancellation2 extends IOApp.Simple { 18 | def run: IO[Unit] = { 19 | val calc1 = IO.sleep(2.second) *> IO.println("Running calc 1") *> IO.pure(42) 20 | val calc2 = IO.sleep(1.second) *> IO.println("Running calc 2") *> IO.pure(421) 21 | 22 | // (calc1, calc2).parMapN(_ + _) >>= IO.println 23 | // ( 24 | // calc1.onCancel(IO.println("calc 1 cancelled")), 25 | // calc2.onCancel(IO.println("calc 2 cancelled")) 26 | // ).parMapN(_ + _) >>= IO.println 27 | // IO.race(calc1.handleErrorWith(_ => IO.never), calc2) >>= IO.println 28 | IO.race(calc1, IO.sleep(1.seconds)) >>= IO.println // run something and cancel it if it does not finish in time 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/io/Ex05Resource.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect.IOApp 4 | import cats.syntax.flatMap._ 5 | 6 | object Ex05Resource extends IOApp.Simple { 7 | import cats.effect.{IO, Resource} 8 | 9 | import java.io._ 10 | 11 | def inputStream(f: File): Resource[IO, FileInputStream] = Resource.fromAutoCloseable(IO(new FileInputStream(f))) 12 | 13 | def outputStream(f: File): Resource[IO, FileOutputStream] = Resource.fromAutoCloseable(IO(new FileOutputStream(f))) 14 | 15 | def inputOutputStreams(in: File, out: File): Resource[IO, (InputStream, OutputStream)] = 16 | for { 17 | inStream <- inputStream(in) 18 | outStream <- outputStream(out) 19 | } yield (inStream, outStream) 20 | 21 | def transmit(origin: InputStream, destination: OutputStream, buffer: Array[Byte], acc: Long): IO[Long] = for { 22 | amount <- IO.blocking(origin.read(buffer, 0, buffer.length)) 23 | count <- 24 | if (amount > -1) 25 | IO.blocking(destination.write(buffer, 0, amount)) >> 26 | transmit(origin, destination, buffer, acc + amount) 27 | else IO.pure(acc) 28 | } yield count 29 | 30 | def transfer(origin: InputStream, destination: OutputStream): IO[Long] = 31 | transmit(origin, destination, new Array[Byte](1024 * 10), 0L) 32 | 33 | def copy(origin: File, destination: File): IO[Long] = 34 | inputOutputStreams(origin, destination).use { case (in, out) => 35 | transfer(in, out) 36 | } 37 | 38 | def run: IO[Unit] = 39 | copy(new File("build.sbt"), new File("build-copy.sbt")) >>= 40 | (c => IO.println(s"Transfered $c bytes")) 41 | } 42 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/io/Ex06SharedConcurrentAccess.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect._ 4 | import cats.effect.implicits._ 5 | import cats.effect.std.Queue 6 | import cats.implicits._ 7 | 8 | class Channel[A](ref: Ref[IO, Int], q: Queue[IO, A]) { 9 | def take: IO[A] = q.take <* ref.update(_ - 1) 10 | 11 | def put(a: A): IO[Unit] = ref.update(_ + 1) >> q.offer(a) 12 | 13 | def append(as: Seq[A]): IO[Unit] = 14 | ref.update(_ + as.length) >> 15 | as.traverse(q.offer).void 16 | 17 | def length: IO[Int] = ref.get 18 | } 19 | 20 | object Channel { 21 | def apply[A](): IO[Channel[A]] = for { 22 | ref <- IO.ref(0) 23 | q <- Queue.unbounded[IO, A] 24 | } yield new Channel(ref, q) 25 | } 26 | 27 | object Ex06SharedConcurrentAccess { 28 | // See tests in ChannelTestSuite 29 | } -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/json/semiauto/Tweet.scala: -------------------------------------------------------------------------------- 1 | package json.semiauto 2 | 3 | import io.circe.Codec 4 | import io.circe.parser._ 5 | import io.circe.syntax._ 6 | 7 | case class Tweet(id: Int, content: String, likes: Int = 0) 8 | 9 | object Tweet { 10 | import io.circe.generic.semiauto._ 11 | 12 | // or both at the same time 13 | implicit val tweetCodec: Codec[Tweet] = deriveCodec 14 | 15 | } 16 | 17 | object DerivedCodecExample extends App { 18 | val tweet = Tweet(1, "Some random content", 123124) 19 | 20 | println(tweet.asJson) 21 | 22 | val json = 23 | """ 24 | |{ 25 | | "id" : 1, 26 | | "content" : "Some random content", 27 | | "likes" : 123124 28 | |}""".stripMargin 29 | 30 | val decodedTweet = decode[Tweet](json) 31 | println(decodedTweet) 32 | } 33 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularity/MyApplication.scala: -------------------------------------------------------------------------------- 1 | package modularity 2 | 3 | import com.typesafe.config.{Config, ConfigFactory} 4 | import modularity.a.{A1, A2, A3, AModule} 5 | import modularity.b.{B1, B2, BModule} 6 | import modularity.c.{C, CModule} 7 | import modularity.d.DModule 8 | 9 | object MyApplication extends CModule with BModule with AModule with DModule { 10 | lazy val config: Config = ConfigFactory.load() 11 | 12 | def main(args: Array[String]): Unit = { 13 | c.doSomething() 14 | println(d) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularity/a/A1.scala: -------------------------------------------------------------------------------- 1 | package modularity.a 2 | 3 | class A1 -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularity/a/A2.scala: -------------------------------------------------------------------------------- 1 | package modularity.a 2 | 3 | class A2 -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularity/a/A3.scala: -------------------------------------------------------------------------------- 1 | package modularity.a 2 | 3 | class A3(a1: A1, a2: A2) { 4 | println(a1, a2) 5 | } -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularity/a/AModule.scala: -------------------------------------------------------------------------------- 1 | package modularity.a 2 | 3 | trait AModule { 4 | lazy val a3 = new A3(a1, a2) 5 | lazy val a1 = new A1 6 | lazy val a2 = new A2 7 | } 8 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularity/b/B1.scala: -------------------------------------------------------------------------------- 1 | package modularity.b 2 | 3 | class B1 -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularity/b/B2.scala: -------------------------------------------------------------------------------- 1 | package modularity.b 2 | 3 | import modularity.a.A1 4 | 5 | class B2(b1: B1, a1: A1) 6 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularity/b/BModule.scala: -------------------------------------------------------------------------------- 1 | package modularity.b 2 | 3 | import modularity.a.A1 4 | 5 | trait BModule { 6 | def a1: A1 7 | 8 | lazy val b1 = { 9 | new B1 10 | } 11 | lazy val b2 = new B2(b1, a1) 12 | } 13 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularity/c/C.scala: -------------------------------------------------------------------------------- 1 | package modularity.c 2 | 3 | import modularity.a.A3 4 | import modularity.b.B2 5 | 6 | class C(a3: A3, b2: B2) { 7 | def doSomething() = println("Hello from C" + a3) 8 | } -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularity/c/CModule.scala: -------------------------------------------------------------------------------- 1 | package modularity.c 2 | 3 | import modularity.a.A3 4 | import modularity.b.{B2, BModule} 5 | 6 | trait CModule { 7 | def a3: A3 8 | def b2: B2 9 | 10 | lazy val c = new C(a3, b2) 11 | } 12 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularity/d/D.scala: -------------------------------------------------------------------------------- 1 | package modularity.d 2 | 3 | import com.typesafe.config.Config 4 | 5 | trait D 6 | class D1 extends D 7 | class D2 extends D 8 | 9 | trait DModule { 10 | def config: Config 11 | 12 | lazy val d: D = 13 | if (config.getString("myApplication.dVersion") == "d1") new D1 14 | else new D2 15 | } 16 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/sql/Doobie01BasicExamples.scala: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import cats.syntax.applicative._ 4 | import cats.syntax.apply._ 5 | import doobie._ 6 | import doobie.implicits._ 7 | 8 | object Doobie01BasicExamples { 9 | val ex1 = 42.pure[ConnectionIO].map(_ + 1) 10 | val ex2 = sql"SELECT 42".query[Int].unique 11 | 12 | val randomSelect = sql"SELECT random()".query[Double].unique 13 | val ex3 = (ex2, randomSelect).tupled 14 | val ex4 = randomSelect.replicateA(10) 15 | } 16 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/sql/Doobie02Querying.scala: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import doobie._ 4 | import doobie.implicits._ 5 | 6 | case class Code(code: String) 7 | case class Country(code: Code, name: String, pop: Int, gnp: Option[Double]) 8 | 9 | object Doobie02Querying { 10 | val ex = 11 | sql""" 12 | SELECT code, name, population, gnp 13 | FROM country 14 | LIMIT 100""" 15 | .query[Country] 16 | .to[List] 17 | 18 | def biggerThan(minPop: Int) = sql""" 19 | select code, name, population, gnp 20 | from country 21 | where population > $minPop 22 | """.query[Country] 23 | 24 | val ex2 = biggerThan(8000000).to[List] 25 | 26 | case class Email(name: String, domain: String) 27 | 28 | implicit val emailMeta = Meta[String] 29 | .imap(_.split("@") match { 30 | case Array(name, domain) => Email(name, domain) 31 | })(e => s"${e.name}@${e.domain}") 32 | 33 | val ex3 = sql"SELECT 'viktor@gmail.com'".query[Email].unique 34 | } 35 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/sql/Doobie03Fragments.scala: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import cats.data.NonEmptyList 4 | import doobie._ 5 | import doobie.implicits._ 6 | 7 | object Doobie03Fragments { 8 | def selectCountries(filter: Fragment): Query0[Country] = { 9 | sql""" 10 | SELECT code, name, population, gnp 11 | FROM country 12 | WHERE $filter 13 | """ 14 | .query[Country] 15 | } 16 | 17 | def populationInRange(range: Range) = { 18 | selectCountries(fr"${range.min} < population AND population < ${range.max}") 19 | } 20 | 21 | val ex1 = populationInRange(100000000 to 300000000).to[List] 22 | 23 | def countriesByCodes(codes: NonEmptyList[String]) = { 24 | selectCountries(Fragments.in(fr"code", codes)) 25 | } 26 | 27 | val ex2 = countriesByCodes(NonEmptyList.of("USA", "BRA", "PAK", "GBR")).to[List] 28 | } 29 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/sql/Doobie04Updates.scala: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import cats.effect.{IO, IOApp} 4 | import cats.syntax.apply._ 5 | import cats.syntax.flatMap._ 6 | import cats.syntax.functor._ 7 | import doobie._ 8 | import doobie.implicits._ 9 | 10 | case class Person(id: Int, name: String, age: Option[Int]) 11 | 12 | object Doobie04Updates extends IOApp.Simple { 13 | val dbTransactor = Transactor.fromDriverManager[IO]( 14 | "org.postgresql.Driver", 15 | "jdbc:postgresql:world", 16 | "postgres", 17 | "" 18 | ) 19 | 20 | def setupPersonTable: ConnectionIO[Unit] = { 21 | val dropPersonTable = 22 | sql""" 23 | DROP TABLE IF EXISTS person 24 | """.update.run 25 | val createPersonTable = 26 | sql""" 27 | CREATE TABLE person ( 28 | id SERIAL, 29 | name VARCHAR NOT NULL UNIQUE, 30 | age SMALLINT 31 | ) 32 | """.update.run 33 | 34 | (dropPersonTable, createPersonTable).tupled.as(()) 35 | } 36 | 37 | def insertPerson(name: String, age: Option[Int]): Update0 = { 38 | sql"""INSERT INTO person(name, age) VALUES ($name, $age)""".update 39 | } 40 | 41 | def insertAndRetrieve(name: String, age: Option[Int]): ConnectionIO[Person] = for { 42 | id <- insertPerson(name, age).withUniqueGeneratedKeys[Int]("id") 43 | person <- sql"SELECT id, name, age FROM person WHERE id = $id".query[Person].unique 44 | } yield person 45 | 46 | def insertAndRetrieveForPostgres(name: String, age: Option[Int]): ConnectionIO[Person] = 47 | insertPerson(name, age).withUniqueGeneratedKeys[Person]("id", "name", "age") 48 | 49 | val insertBoyan = insertPerson("Boyan", None).run 50 | val insertAndRetrieveViktor = insertAndRetrieveForPostgres("Viktor", Some(25)) 51 | 52 | def run: IO[Unit] = 53 | setupPersonTable.transact(dbTransactor) *> 54 | insertBoyan.transact(dbTransactor) *> 55 | insertAndRetrieveViktor.transact(dbTransactor) >>= 56 | IO.println 57 | } 58 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/sql/Doobie05BatchUpdates.scala: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import cats.effect.{IO, IOApp} 4 | import cats.syntax.flatMap._ 5 | import doobie._ 6 | import doobie.implicits._ 7 | import sql.Doobie04Updates.{dbTransactor, setupPersonTable} 8 | 9 | object Doobie05BatchUpdates extends IOApp.Simple { 10 | def insertMany(ps: List[Person]): ConnectionIO[Int] = { 11 | val sql = "INSERT INTO person (id, name, age) VALUES (?, ?, ?)" 12 | Update[Person](sql).updateMany(ps) 13 | } 14 | 15 | val data = List[Person]( 16 | Person(10, "Maya", Some(24)), 17 | Person(20, "Ivan", None) 18 | ) 19 | 20 | val insertData = insertMany(data) 21 | 22 | def run: IO[Unit] = 23 | setupPersonTable.transact(dbTransactor) *> 24 | insertData.transact(dbTransactor) >>= 25 | IO.println 26 | } 27 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/sql/DoobieApp.scala: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import cats.effect.{IO, IOApp} 4 | import cats.syntax.flatMap._ 5 | import doobie._ 6 | import doobie.implicits._ 7 | 8 | object DoobieApp extends IOApp.Simple { 9 | val dbTransactor = Transactor.fromDriverManager[IO]( 10 | "org.postgresql.Driver", 11 | "jdbc:postgresql:world", 12 | "postgres", 13 | "" 14 | ) 15 | 16 | def run: IO[Unit] = Doobie03Fragments.ex2.transact(dbTransactor) >>= IO.println 17 | } 18 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/streams/Fs201BasicExample.scala: -------------------------------------------------------------------------------- 1 | package streams 2 | 3 | import cats.effect.IO 4 | import cats.effect.unsafe.implicits.global 5 | 6 | object Fs201BasicExample extends App { 7 | import fs2.Stream 8 | 9 | val s1 = Stream.empty 10 | val s2 = Stream.emit(1) 11 | val s3 = Stream(1, 2, 3) 12 | val s4 = Stream.emits(List(1, 2, 3)) 13 | 14 | val s5 = s3 ++ s4 15 | 16 | println(s5.toList) 17 | 18 | def repeat[F[_], A](stream: Stream[F, A]): Stream[F, A] = { 19 | stream ++ repeat(stream) 20 | } 21 | 22 | println(repeat(s3).take(10).toList) 23 | 24 | val effectfulStream = Stream.eval(IO.println("Hellou!!!")) 25 | println(effectfulStream.compile.toList.unsafeRunSync()) 26 | 27 | val effectfulStream2 = Stream.evalSeq(IO(List(1, 2, 3))) 28 | println(effectfulStream2.compile.fold(0)(_ + _).unsafeRunSync()) 29 | } 30 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/streams/Fs202Files.scala: -------------------------------------------------------------------------------- 1 | package streams 2 | 3 | import cats.effect.{IO, IOApp} 4 | import fs2.io.file.Files 5 | import fs2.{Stream, text} 6 | 7 | import java.nio.file.Paths 8 | 9 | object Fs202Files extends IOApp.Simple { 10 | def converter(inputFile: String, outputFile: String): Stream[IO, Unit] = { 11 | def fahrenheitToCelsius(f: Double): Double = 12 | (f - 32.0) * (5.0 / 9.0) 13 | 14 | Files[IO].readAll(Paths.get(inputFile), 4096) 15 | .through(text.utf8Decode) 16 | .through(text.lines) 17 | .filter(s => s.trim.nonEmpty && !s.startsWith("//")) 18 | .map(line => fahrenheitToCelsius(line.toDouble).toString) 19 | .intersperse("\n") 20 | .through(text.utf8Encode) 21 | .through(Files[IO].writeAll(Paths.get(outputFile))) 22 | } 23 | 24 | def run: IO[Unit] = converter("fahrenheit.txt", "celsius.txt").compile.drain 25 | } 26 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/streams/Fs203Http.scala: -------------------------------------------------------------------------------- 1 | package streams 2 | 3 | import cats.effect.{IO, IOApp} 4 | import fs2.Stream 5 | import org.http4s.HttpRoutes 6 | import org.http4s.blaze.server.BlazeServerBuilder 7 | import org.http4s.dsl.io._ 8 | import org.http4s.implicits._ 9 | 10 | import scala.concurrent.ExecutionContext.global 11 | import scala.concurrent.duration.DurationInt 12 | 13 | object Fs203Http extends IOApp.Simple { 14 | val countToTen: Stream[IO, String] = 15 | Stream.awakeEvery[IO](1.second) 16 | .map(_.toString + "\n") 17 | .take(10) 18 | 19 | val counterRoutes = HttpRoutes.of[IO] { 20 | case GET -> Root / "counter" => 21 | Ok(countToTen) 22 | } 23 | 24 | val httpApp = counterRoutes.orNotFound 25 | 26 | val serverBuilder = BlazeServerBuilder[IO](global) 27 | .bindHttp(8080, "localhost") 28 | .withHttpApp(httpApp) 29 | .resource 30 | 31 | def run: IO[Unit] = serverBuilder.use(_ => IO.never) 32 | } 33 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/streams/Fs204HttpWithDoobie.scala: -------------------------------------------------------------------------------- 1 | package streams 2 | 3 | import cats.effect.{IO, IOApp} 4 | import fs2.Stream 5 | import org.http4s.HttpRoutes 6 | import org.http4s.blaze.server.BlazeServerBuilder 7 | import org.http4s.dsl.io._ 8 | import org.http4s.implicits._ 9 | import org.http4s.server.Router 10 | import sql.{Country, DoobieApp} 11 | import doobie._ 12 | import doobie.implicits._ 13 | 14 | import scala.concurrent.ExecutionContext.global 15 | import scala.concurrent.duration.DurationInt 16 | 17 | case class City(name: String, country: String, population: Int) 18 | 19 | object Fs204HttpWithDoobie extends IOApp.Simple { 20 | val allCities = 21 | sql""" 22 | SELECT name, countrycode, population 23 | FROM city 24 | """ 25 | .query[City] 26 | .stream 27 | .transact(DoobieApp.dbTransactor) 28 | 29 | val count: Stream[IO, String] = 30 | Stream.awakeEvery[IO](100.millis) 31 | .map(_.toString + "\n") 32 | 33 | val routes = HttpRoutes.of[IO] { 34 | case GET -> Root / "counter" => 35 | Ok(count.take(10)) 36 | case GET -> Root / "cities" => 37 | Ok(allCities.map(_.toString + "\n")) 38 | case GET -> Root / "combined" => 39 | val output = (count zip allCities).map { case (elapsedTime, city) => 40 | elapsedTime.toString + city.toString + "\n" 41 | } 42 | 43 | Ok(output) 44 | } 45 | 46 | val httpApp = routes.orNotFound 47 | 48 | val serverBuilder = BlazeServerBuilder[IO](global) 49 | .bindHttp(8080, "localhost") 50 | .withHttpApp(httpApp) 51 | .resource 52 | 53 | def run: IO[Unit] = serverBuilder.use(_ => IO.never) 54 | } 55 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/streams/Fs205WebSockets.scala: -------------------------------------------------------------------------------- 1 | package streams 2 | 3 | import cats.effect._ 4 | import fs2.{Pipe, Stream} 5 | import org.http4s._ 6 | import org.http4s.blaze.server.BlazeServerBuilder 7 | import org.http4s.dsl.io._ 8 | import org.http4s.implicits._ 9 | import org.http4s.server.websocket.WebSocketBuilder 10 | import org.http4s.websocket.WebSocketFrame 11 | import org.http4s.websocket.WebSocketFrame.Text 12 | 13 | import scala.concurrent.ExecutionContext.global 14 | 15 | object Fs205WebSocket extends IOApp.Simple { 16 | def routes: HttpRoutes[IO] = HttpRoutes.of[IO] { 17 | case GET -> Root / "echo-ws" => 18 | val echoReply: Pipe[IO, WebSocketFrame, WebSocketFrame] = 19 | _.flatMap { 20 | case Text(msg, _) => Stream( 21 | Text(s"You sent the server: $msg."), 22 | Text("Yay :)") 23 | ) 24 | case _ => Stream(Text("You sent something different than text")) 25 | } 26 | 27 | WebSocketBuilder[IO].build(echoReply) 28 | } 29 | 30 | val httpApp = routes.orNotFound 31 | 32 | val serverBuilder = BlazeServerBuilder[IO](global) 33 | .bindHttp(8080, "localhost") 34 | .withHttpApp(httpApp) 35 | .resource 36 | 37 | def run: IO[Unit] = serverBuilder.use(_ => IO.never) 38 | } 39 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/test/scala/http/HelloSpec.scala: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import cats.effect.IO 4 | import cats.effect.testing.scalatest.AsyncIOSpec 5 | import http.Example1_HttpRoutes.httpApp 6 | import org.http4s._ 7 | import org.http4s.implicits._ 8 | import org.scalatest.flatspec.AsyncFlatSpec 9 | import org.scalatest.matchers.should.Matchers 10 | 11 | class HelloSpec extends AsyncFlatSpec with AsyncIOSpec with Matchers { 12 | 13 | "hello route" should "return status code 200" in { 14 | val res = httpApp(get(uri"/hello/world")) 15 | res.map(_.status).asserting(_ shouldBe Status.Ok) 16 | } 17 | 18 | it should "return hello world message" in { 19 | val res = httpApp(get(uri"/hello/world")) 20 | res.flatMap(_.as[String]).asserting(_ shouldBe "Hello, world.") 21 | } 22 | 23 | "hola route" should "return ¡Hola, Mundo! message" in { 24 | val res = httpApp(get(uri"/hola/Mundo")) 25 | res.flatMap(_.as[String]).asserting(_ shouldBe "¡Hola, Mundo!") 26 | } 27 | 28 | private def get(uri: Uri): Request[IO] = Request[IO](Method.GET, uri) 29 | } 30 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/test/scala/io/ChannelTestSuite.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect._ 4 | import cats.effect.testing.scalatest.AsyncIOSpec 5 | import cats.implicits._ 6 | import org.scalatest.freespec.AsyncFreeSpec 7 | import org.scalatest.matchers.should.Matchers 8 | 9 | import scala.concurrent.TimeoutException 10 | import scala.concurrent.duration.DurationInt 11 | 12 | class ChannelTestSuite extends AsyncFreeSpec with AsyncIOSpec with Matchers { 13 | "Single put take passes" in { 14 | val result = for { 15 | chan <- Channel[Int]() 16 | _ <- chan.put(1) 17 | v <- chan.take 18 | } yield v 19 | 20 | result.asserting(_ shouldBe 1) 21 | } 22 | 23 | "Test appending of multiple values" in { 24 | val result = for { 25 | chan <- Channel[Int]() 26 | _ <- chan.append(List.range(0, 25)) 27 | _ <- chan.take.replicateA(15) 28 | len <- chan.length 29 | } yield len 30 | 31 | result.asserting(_ shouldBe 10) 32 | } 33 | 34 | "We gonna block dawg" in { 35 | val result = for { 36 | chan <- Channel[Int]() 37 | _ <- chan.append(List.range(0, 5)) 38 | maybeError <- chan.take 39 | .replicateA(6) 40 | .timeout(5.seconds) 41 | .as(IO.pure(None)) 42 | .handleErrorWith(e => IO.pure(Some(e))) 43 | } yield maybeError 44 | 45 | result.asserting { 46 | case Some(e) => assert(e.isInstanceOf[TimeoutException]) 47 | case None => fail("Wut?") 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lectures/examples/12-library-app/build.sbt: -------------------------------------------------------------------------------- 1 | name := "library-app" 2 | version := "0.1" 3 | 4 | scalaVersion := "2.13.6" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.typelevel" %% "cats-core" % "2.6.0", 8 | "org.typelevel" %% "cats-effect" % "3.1.1", 9 | 10 | "org.http4s" %% "http4s-dsl" % "0.23.0-RC1", 11 | "org.http4s" %% "http4s-blaze-server" % "0.23.0-RC1", 12 | "org.http4s" %% "http4s-blaze-client" % "0.23.0-RC1", 13 | "org.http4s" %% "http4s-circe" % "0.23.0-RC1", 14 | 15 | "io.circe" %% "circe-generic" % "0.14.0", 16 | "io.circe" %% "circe-generic-extras" % "0.14.0", 17 | 18 | "ch.qos.logback" % "logback-classic" % "1.2.3", 19 | 20 | "org.scalatest" %% "scalatest" % "3.2.7" % Test 21 | ) 22 | -------------------------------------------------------------------------------- /lectures/examples/12-library-app/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.1 2 | -------------------------------------------------------------------------------- /lectures/examples/12-library-app/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | true 9 | 10 | [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /lectures/examples/12-library-app/src/main/scala/fmi/LibraryClient.scala: -------------------------------------------------------------------------------- 1 | package fmi 2 | 3 | import cats.effect.kernel.Resource 4 | import cats.effect.{IO, IOApp} 5 | import cats.implicits._ 6 | import fmi.client.{LibraryApi, LibraryClientUI} 7 | import fmi.library.{Author, Book} 8 | import org.http4s.blaze.client.BlazeClientBuilder 9 | 10 | case class BookWithAuthors(book: Book, authors: List[Author]) 11 | 12 | object LibraryClient extends IOApp.Simple { 13 | val app = for { 14 | computeExecutionContext <- Resource.liftK(IO.executionContext) 15 | 16 | client <- BlazeClientBuilder[IO](computeExecutionContext).resource 17 | 18 | libraryApi = new LibraryApi(client) 19 | libraryClientUI = new LibraryClientUI(libraryApi) 20 | } yield libraryClientUI 21 | 22 | def run: IO[Unit] = 23 | app.use(ui => ui.selectBook) 24 | .onCancel(IO.println("Thank you for browsing our library :)!")) 25 | } 26 | -------------------------------------------------------------------------------- /lectures/examples/12-library-app/src/main/scala/fmi/LibraryServer.scala: -------------------------------------------------------------------------------- 1 | package fmi 2 | 3 | import cats.effect.kernel.Resource 4 | import cats.effect.{IO, IOApp} 5 | import cats.implicits._ 6 | import fmi.server.LibraryHttpApp 7 | import org.http4s.blaze.server.BlazeServerBuilder 8 | 9 | object LibraryServer extends IOApp.Simple { 10 | val server = for { 11 | computeExecutionContext <- Resource.liftK(IO.executionContext) 12 | 13 | server <- BlazeServerBuilder[IO](computeExecutionContext) 14 | .bindHttp(8080, "localhost") 15 | .withHttpApp(LibraryHttpApp.libraryApp) 16 | .resource 17 | } yield server 18 | 19 | def run: IO[Unit] = 20 | server.use(_ => IO.never) 21 | .onCancel(IO.println("Bye, see you again \uD83D\uDE0A")) 22 | } 23 | -------------------------------------------------------------------------------- /lectures/examples/12-library-app/src/main/scala/fmi/client/LibraryApi.scala: -------------------------------------------------------------------------------- 1 | package fmi.client 2 | 3 | import cats.effect.IO 4 | import cats.implicits._ 5 | import fmi.BookWithAuthors 6 | import fmi.library._ 7 | import io.circe.Decoder 8 | import org.http4s.client.Client 9 | 10 | class LibraryApi(client: Client[IO]) { 11 | import fmi.codecs.LibraryCodecs._ 12 | import org.http4s.circe.CirceEntityCodec._ 13 | 14 | private def retrieve[A : Decoder](path: String): IO[A] = client.expect[A](s"http://localhost:8080/$path") 15 | 16 | def listBooks: IO[List[BookSummary]] = retrieve[List[BookSummary]]("books") 17 | 18 | def retrieveAuthor(authorId: AuthorId): IO[Author] = retrieve[Author](s"authors/${authorId.id}") 19 | 20 | def retrieveBookWithAuthors(bookId: BookId): IO[BookWithAuthors] = for { 21 | book <- retrieve[Book](s"books/${bookId.id}") 22 | authors <- book.authors.parTraverse(authorId => retrieveAuthor(authorId)) 23 | } yield BookWithAuthors(book, authors) 24 | } 25 | -------------------------------------------------------------------------------- /lectures/examples/12-library-app/src/main/scala/fmi/codecs/LibraryCodecs.scala: -------------------------------------------------------------------------------- 1 | package fmi.codecs 2 | 3 | import fmi.library.{Author, AuthorId, Book, BookId, BookSummary} 4 | import io.circe.Codec 5 | 6 | object LibraryCodecs { 7 | import io.circe.generic.extras.semiauto.deriveUnwrappedCodec 8 | import io.circe.generic.semiauto._ 9 | 10 | implicit val authorIdCodec: Codec[AuthorId] = deriveUnwrappedCodec 11 | implicit val authorCodec: Codec[Author] = deriveCodec 12 | 13 | implicit val bookIdCodec: Codec[BookId] = deriveUnwrappedCodec 14 | implicit val bookCodec: Codec[Book] = deriveCodec 15 | 16 | implicit val bookSummaryCodec: Codec[BookSummary] = deriveCodec 17 | } 18 | -------------------------------------------------------------------------------- /lectures/examples/12-library-app/src/main/scala/fmi/library/BookSummary.scala: -------------------------------------------------------------------------------- 1 | package fmi.library 2 | 3 | object BookSummary { 4 | def apply(book: Book): BookSummary = BookSummary(book.id, book.name) 5 | } 6 | 7 | case class BookSummary(id: BookId, name: String) -------------------------------------------------------------------------------- /lectures/examples/12-library-app/src/main/scala/fmi/library/Library.scala: -------------------------------------------------------------------------------- 1 | package fmi.library 2 | 3 | import cats.effect.IO 4 | 5 | case class BookId(id: String) extends AnyVal 6 | case class Book(id: BookId, name: String, authors: List[AuthorId], genre: String) 7 | 8 | case class AuthorId(id: String) extends AnyVal 9 | case class Author(id: AuthorId, name: String) 10 | 11 | class Library(books: List[Book], authors: List[Author]) { 12 | private val bookIdToBook = books.map(book => book.id -> book).toMap 13 | private val authorIdToAuthor = authors.map(author => author.id -> author).toMap 14 | 15 | def findBook(bookId: BookId): IO[Option[Book]] = IO.pure(bookIdToBook.get(bookId)) 16 | 17 | def findAuthor(authorId: AuthorId): IO[Option[Author]] = IO.pure(authorIdToAuthor.get(authorId)) 18 | 19 | def allBooks: IO[List[Book]] = IO.pure(bookIdToBook.values.toList) 20 | } 21 | 22 | object Library { 23 | private val books = List( 24 | Book(BookId("1"), "Programming in Scala", List(AuthorId("1"), AuthorId("2")), "Computer Science"), 25 | Book(BookId("2"), "Programming Erlang", List(AuthorId("3")), "Computer Science"), 26 | Book(BookId("3"), "American Gods", List(AuthorId("4")), "Fantasy"), 27 | Book(BookId("4"), "The Fellowship of the Ring", List(AuthorId("5")), "Fantasy"), 28 | Book(BookId("5"), "The Book", List( 29 | AuthorId("1"), AuthorId("3"), AuthorId("4"), AuthorId("5") 30 | ), "Fantasy") 31 | ) 32 | private val authors = List( 33 | Author(AuthorId("1"), "Martin Odersky"), 34 | Author(AuthorId("2"), "Bill Venners"), 35 | Author(AuthorId("3"), "Joe Armstrong"), 36 | Author(AuthorId("4"), "Neil Gaiman"), 37 | Author(AuthorId("5"), "J. R. R. Tolkien") 38 | ) 39 | 40 | val TheGreatLibrary = new Library(books, authors) 41 | } 42 | -------------------------------------------------------------------------------- /lectures/examples/12-library-app/src/main/scala/fmi/server/LibraryHttpApp.scala: -------------------------------------------------------------------------------- 1 | package fmi.server 2 | 3 | import cats.effect.IO 4 | import cats.implicits._ 5 | import fmi.library.Library.TheGreatLibrary 6 | import fmi.library.{AuthorId, BookId, BookSummary} 7 | import org.http4s.HttpRoutes 8 | import org.http4s.dsl.io._ 9 | import org.http4s.implicits._ 10 | 11 | object LibraryHttpApp { 12 | import fmi.codecs.LibraryCodecs._ 13 | import org.http4s.circe.CirceEntityCodec._ 14 | 15 | val bookRoutes = HttpRoutes.of[IO] { 16 | case GET -> Root / "books" => 17 | val books = TheGreatLibrary.allBooks.nested.map(BookSummary(_)).value 18 | 19 | Ok(books) 20 | case GET -> Root / "books" / bookIdSegment => 21 | val bookId = BookId(bookIdSegment) 22 | 23 | TheGreatLibrary.findBook(bookId).flatMap( 24 | _.fold(NotFound())(book => Ok(book)) 25 | ) 26 | } 27 | 28 | val authorRoutes = HttpRoutes.of[IO] { 29 | case GET -> Root / "authors" / authorIdSegment => 30 | val authorId = AuthorId(authorIdSegment) 31 | 32 | TheGreatLibrary.findAuthor(authorId).flatMap( 33 | _.fold(NotFound())(author => Ok(author)) 34 | ) 35 | } 36 | 37 | val libraryApp = (bookRoutes <+> authorRoutes).orNotFound 38 | } 39 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/build.sbt: -------------------------------------------------------------------------------- 1 | name := "shopping-app" 2 | version := "0.1" 3 | 4 | scalaVersion := "2.13.6" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.typelevel" %% "cats-core" % "2.6.0", 8 | "org.typelevel" %% "cats-effect" % "3.1.1", 9 | 10 | "co.fs2" %% "fs2-core" % "3.0.4", 11 | "co.fs2" %% "fs2-io" % "3.0.4", 12 | "co.fs2" %% "fs2-reactive-streams" % "3.0.4", 13 | 14 | "org.http4s" %% "http4s-dsl" % "0.23.0-RC1", 15 | "org.http4s" %% "http4s-blaze-server" % "0.23.0-RC1", 16 | "org.http4s" %% "http4s-blaze-client" % "0.23.0-RC1", 17 | "org.http4s" %% "http4s-circe" % "0.23.0-RC1", 18 | 19 | "org.flywaydb" % "flyway-core" % "7.9.1", 20 | 21 | "org.tpolecat" %% "doobie-core" % "1.0.0-M5", 22 | "org.tpolecat" %% "doobie-hikari" % "1.0.0-M5", 23 | "org.tpolecat" %% "doobie-postgres" % "1.0.0-M5", 24 | 25 | "com.typesafe" % "config" % "1.4.1", 26 | 27 | "io.circe" %% "circe-generic" % "0.14.0", 28 | "io.circe" %% "circe-generic-extras" % "0.14.0", 29 | "io.circe" %% "circe-config" % "0.8.0", 30 | 31 | "org.mindrot" % "jbcrypt" % "0.3m", 32 | 33 | "org.reactormonk" %% "cryptobits" % "1.3", 34 | 35 | "ch.qos.logback" % "logback-classic" % "1.2.3", 36 | 37 | "org.scalatest" %% "scalatest" % "3.2.7" % Test, 38 | "org.typelevel" %% "cats-laws" % "2.6.0" % Test, 39 | "org.typelevel" %% "discipline-scalatest" % "2.1.5" % Test, 40 | ) 41 | 42 | fork := true 43 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.1 2 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | shoppingApp { 2 | secretKey = "fGGq`JY0KBR?>TVA=dcrBKqV2wMeUXtYh/f_U^nu3]QeB@THW[[J8kq54" 3 | 4 | http { 5 | port = 8080 6 | } 7 | 8 | database = { 9 | host = "localhost" 10 | port = "5432" 11 | name = "shoppingapp" 12 | user = "shoppingapp" 13 | password = "secret-P@assw0rd" 14 | schema = "shoppingapp" 15 | connectionPoolSize = 10 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/resources/db-migrations/V1.1__add_inventory_tables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE product ( 2 | sku TEXT PRIMARY KEY, 3 | name TEXT NOT NULL, 4 | description TEXT NOT NULL, 5 | weight_in_grams INT NOT NULL 6 | ); 7 | 8 | CREATE TABLE product_stock ( 9 | sku TEXT PRIMARY KEY, 10 | quantity INT NOT NULL 11 | ); 12 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/resources/db-migrations/V1.2__add_order_tables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "order" ( 2 | id TEXT PRIMARY KEY, 3 | user_id TEXT NOT NULL, 4 | placing_timestamp timestamptz NOT NULL 5 | ); 6 | 7 | CREATE TABLE order_line ( 8 | order_id TEXT NOT NULL, 9 | sku TEXT NOT NULL, 10 | quantity INT NOT NULL, 11 | 12 | PRIMARY KEY(order_id, sku) 13 | ); 14 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/resources/db-migrations/V1__init.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "user" ( 2 | email TEXT PRIMARY KEY, 3 | password_hash TEXT NOT NULL, 4 | role TEXT NOT NULL, 5 | name TEXT NOT NULL, 6 | age INT 7 | ); 8 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | true 9 | 10 | [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/ShoppingApp.scala: -------------------------------------------------------------------------------- 1 | package fmi 2 | 3 | import cats.effect.kernel.Resource 4 | import cats.effect.{IO, IOApp} 5 | import cats.implicits._ 6 | import fmi.config.ConfigJsonCodecs._ 7 | import fmi.config.ShoppingAppConfig 8 | import fmi.infrastructure.CryptoService 9 | import fmi.infrastructure.db.DbModule 10 | import fmi.inventory.InventoryModule 11 | import fmi.shopping.ShoppingModule 12 | import fmi.user.UsersModule 13 | import io.circe.config.parser 14 | import org.http4s.HttpRoutes 15 | import org.http4s.blaze.server.BlazeServerBuilder 16 | import org.http4s.implicits._ 17 | import org.http4s.server.Server 18 | 19 | object ShoppingApp extends IOApp.Simple { 20 | val app: Resource[IO, Server] = for { 21 | config <- Resource.eval(parser.decodePathF[IO, ShoppingAppConfig]("shoppingApp")) 22 | computeExecutionContext <- Resource.liftK(IO.executionContext) 23 | 24 | cryptoService = new CryptoService(config.secretKey) 25 | 26 | dbModule <- DbModule(config.database) 27 | 28 | usersModule <- UsersModule(dbModule.dbTransactor, cryptoService) 29 | inventoryModule <- InventoryModule(dbModule.dbTransactor) 30 | shoppingModule <- ShoppingModule(dbModule.dbTransactor, inventoryModule.productStockDao) 31 | 32 | nonAuthenticatedRoutes = usersModule.routes <+> inventoryModule.routes 33 | authenticatedRoutes = usersModule.authMiddleware { 34 | usersModule.authenticatedRoutes <+> inventoryModule.authenticatedRoutes <+> shoppingModule.authenticatedRoutes 35 | } 36 | 37 | routes = (nonAuthenticatedRoutes <+> authenticatedRoutes).orNotFound 38 | 39 | httpServer <- BlazeServerBuilder[IO](computeExecutionContext) 40 | .bindHttp(config.http.port, "localhost") 41 | .withHttpApp(routes) 42 | .resource 43 | } yield httpServer 44 | 45 | def run: IO[Unit] = 46 | app.use(_ => IO.never) 47 | .onCancel(IO.println("Bye, see you again \uD83D\uDE0A")) 48 | } 49 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/config/HttpConfig.scala: -------------------------------------------------------------------------------- 1 | package fmi.config 2 | 3 | case class HttpConfig(port: Int) 4 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/config/ShoppingAppConfig.scala: -------------------------------------------------------------------------------- 1 | package fmi.config 2 | 3 | import fmi.infrastructure.db.DbConfig 4 | import io.circe.Codec 5 | 6 | case class ShoppingAppConfig( 7 | secretKey: String, 8 | http: HttpConfig, 9 | database: DbConfig 10 | ) 11 | 12 | object ConfigJsonCodecs { 13 | import io.circe.generic.semiauto._ 14 | 15 | implicit val httpConfigCodec: Codec[HttpConfig] = deriveCodec 16 | implicit val dbConfigCodec: Codec[DbConfig] = deriveCodec 17 | implicit val shoppingAppConfigCodec: Codec[ShoppingAppConfig] = deriveCodec 18 | } 19 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/infrastructure/CryptoService.scala: -------------------------------------------------------------------------------- 1 | package fmi.infrastructure 2 | 3 | import org.reactormonk.{CryptoBits, PrivateKey} 4 | 5 | class CryptoService(privateKey: String) { 6 | private val key = PrivateKey(scala.io.Codec.toUTF8(privateKey)) 7 | private val crypto = CryptoBits(key) 8 | 9 | def encrypt(str: String): String = { 10 | crypto.signToken(str, System.currentTimeMillis().toString) // TODO: wrap it in referentially transparent IO 11 | } 12 | 13 | def decrypt(str: String): Option[String] = { 14 | crypto.validateSignedToken(str) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/infrastructure/db/DbConfig.scala: -------------------------------------------------------------------------------- 1 | package fmi.infrastructure.db 2 | 3 | case class DbConfig(host: String, 4 | port: Int, 5 | user: String, 6 | password: String, 7 | name: String, 8 | schema: String, 9 | connectionPoolSize: Int) { 10 | def jdbcUrl: String = s"jdbc:postgresql://$host:$port/$name" 11 | } 12 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/infrastructure/db/DbMigrator.scala: -------------------------------------------------------------------------------- 1 | package fmi.infrastructure.db 2 | 3 | import cats.effect.IO 4 | import org.flywaydb.core.Flyway 5 | import org.flywaydb.core.api.output.{CleanResult, MigrateResult} 6 | 7 | class DbMigrator(dbConfig: DbConfig, migrationsLocation: String) { 8 | private val flyway: Flyway = Flyway 9 | .configure() 10 | .dataSource(dbConfig.jdbcUrl, dbConfig.user, dbConfig.password) 11 | .schemas(dbConfig.schema) 12 | .locations(migrationsLocation) 13 | .table("flyway_schema_history") 14 | .baselineOnMigrate(true) 15 | .load() 16 | 17 | def migrate(): IO[MigrateResult] = IO.blocking(flyway.migrate()) 18 | 19 | def clean(): IO[CleanResult] = IO.blocking(flyway.clean()) 20 | } 21 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/infrastructure/db/DbModule.scala: -------------------------------------------------------------------------------- 1 | package fmi.infrastructure.db 2 | 3 | import cats.effect.IO 4 | import cats.effect.kernel.Resource 5 | import doobie.ExecutionContexts 6 | import doobie.hikari.HikariTransactor 7 | import fmi.infrastructure.db.DoobieDatabase.DbTransactor 8 | 9 | case class DbModule(dbTransactor: DbTransactor) 10 | 11 | object DbModule { 12 | def apply(dbConfig: DbConfig): Resource[IO, DbModule] = for { 13 | _ <- Resource.eval(new DbMigrator(dbConfig, "classpath:/db-migrations").migrate()) 14 | 15 | connectionEc <- ExecutionContexts.fixedThreadPool[IO](dbConfig.connectionPoolSize) 16 | transactor <- HikariTransactor.newHikariTransactor[IO]( 17 | "org.postgresql.Driver", 18 | dbConfig.jdbcUrl, 19 | dbConfig.user, 20 | dbConfig.password, 21 | connectionEc 22 | ) 23 | } yield DbModule(transactor) 24 | } 25 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/infrastructure/db/DoobieDatabase.scala: -------------------------------------------------------------------------------- 1 | package fmi.infrastructure.db 2 | 3 | import cats.effect.IO 4 | import doobie.hikari.HikariTransactor 5 | 6 | object DoobieDatabase { 7 | type DbTransactor = HikariTransactor[IO] 8 | } 9 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/inventory/Inventory.scala: -------------------------------------------------------------------------------- 1 | package fmi.inventory 2 | 3 | case class ProductStock(product: ProductSku, quantity: Int) 4 | 5 | case class ProductStockAdjustment(product: ProductSku, adjustmentQuantity: Int) 6 | case class InventoryAdjustment(adjustments: List[ProductStockAdjustment]) 7 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/inventory/InventoryModule.scala: -------------------------------------------------------------------------------- 1 | package fmi.inventory 2 | 3 | import cats.effect.IO 4 | import cats.effect.kernel.Resource 5 | import fmi.infrastructure.db.DoobieDatabase.DbTransactor 6 | import fmi.user.AuthenticatedUser 7 | import org.http4s.{AuthedRoutes, HttpRoutes} 8 | 9 | case class InventoryModule( 10 | productDao: ProductDao, 11 | productStockDao: ProductStockDao, 12 | routes: HttpRoutes[IO], 13 | authenticatedRoutes: AuthedRoutes[AuthenticatedUser, IO] 14 | ) 15 | 16 | object InventoryModule { 17 | def apply(dbTransactor: DbTransactor): Resource[IO, InventoryModule] = { 18 | val productDao = new ProductDao(dbTransactor) 19 | val productStockDao = new ProductStockDao(dbTransactor) 20 | val inventoryRouter = new InventoryRouter(productDao, productStockDao) 21 | 22 | Resource.pure(InventoryModule( 23 | productDao, 24 | productStockDao, 25 | inventoryRouter.nonAuthenticatedRoutes, 26 | inventoryRouter.authenticatedRoutes 27 | )) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/inventory/InventoryRouter.scala: -------------------------------------------------------------------------------- 1 | package fmi.inventory 2 | import cats.effect.IO 3 | import cats.implicits._ 4 | import fmi.user.{AuthenticatedUser, UserRole} 5 | import io.circe.Codec 6 | import io.circe.generic.extras.semiauto.deriveUnwrappedCodec 7 | import io.circe.generic.semiauto.deriveCodec 8 | import org.http4s.dsl.io._ 9 | import org.http4s.{AuthedRoutes, HttpRoutes} 10 | 11 | class InventoryRouter(productDao: ProductDao, productStockDao: ProductStockDao) { 12 | import InventoryJsonCodecs._ 13 | import org.http4s.circe.CirceEntityCodec._ 14 | 15 | def nonAuthenticatedRoutes: HttpRoutes[IO] = HttpRoutes.of[IO] { 16 | case GET -> Root / "stock" => 17 | Ok(productStockDao.retrieveAllAvailableStock) 18 | 19 | case GET -> Root / "products" / sku => 20 | val productSku = ProductSku(sku) 21 | 22 | productDao.retrieveProduct(productSku).flatMap( 23 | _.fold(NotFound())(Ok(_)) 24 | ) 25 | } 26 | 27 | def authenticatedRoutes: AuthedRoutes[AuthenticatedUser, IO] = AuthedRoutes.of[AuthenticatedUser, IO] { 28 | case authReq @ POST -> Root / "products" as user if user.role == UserRole.Admin => 29 | Ok(authReq.req.as[Product] >>= productDao.addProduct) 30 | 31 | case authReq @ POST -> Root / "stock" as user if user.role == UserRole.Admin => 32 | val adjustmentResult = authReq.req.as[InventoryAdjustment] >>= productStockDao.applyInventoryAdjustment 33 | 34 | adjustmentResult.flatMap { 35 | case SuccessfulAdjustment => Ok() 36 | case NotEnoughStockAvailable => Conflict() 37 | } 38 | } 39 | } 40 | 41 | object InventoryJsonCodecs { 42 | implicit val productSkuCodec: Codec[ProductSku] = deriveUnwrappedCodec 43 | implicit val productCodec: Codec[Product] = deriveCodec 44 | implicit val productStockCodec: Codec[ProductStock] = deriveCodec 45 | 46 | implicit val productStockAdjustmentCodec: Codec[ProductStockAdjustment] = deriveCodec 47 | implicit val inventoryAdjustmentCodec: Codec[InventoryAdjustment] = deriveCodec 48 | } 49 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/inventory/Product.scala: -------------------------------------------------------------------------------- 1 | package fmi.inventory 2 | 3 | case class ProductSku(sku: String) 4 | case class Product(sku: ProductSku, name: String, description: String, weightInGrams: Int) 5 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/inventory/ProductDao.scala: -------------------------------------------------------------------------------- 1 | package fmi.inventory 2 | 3 | import cats.effect.IO 4 | import cats.implicits._ 5 | import doobie.implicits._ 6 | import fmi.infrastructure.db.DoobieDatabase.DbTransactor 7 | 8 | class ProductDao(dbTransactor: DbTransactor) { 9 | def retrieveProduct(sku: ProductSku): IO[Option[Product]] = { 10 | sql""" 11 | SELECT sku, name, description, weight_in_grams 12 | FROM product 13 | WHERE sku = $sku 14 | """ 15 | .query[Product] 16 | .option 17 | .transact(dbTransactor) 18 | } 19 | 20 | def addProduct(product: Product): IO[Unit] = { 21 | sql""" 22 | INSERT INTO product as p (sku, name, description, weight_in_grams) 23 | VALUES (${product.sku}, ${product.name}, ${product.description}, ${product.weightInGrams}) 24 | ON CONFLICT (sku) DO UPDATE SET 25 | name = EXCLUDED.name, 26 | description = EXCLUDED.description, 27 | weight_in_grams = EXCLUDED.weight_in_grams 28 | """ 29 | .update 30 | .run 31 | .void 32 | .transact(dbTransactor) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/shopping/Order.scala: -------------------------------------------------------------------------------- 1 | package fmi.shopping 2 | 3 | import cats.effect.IO 4 | import fmi.inventory.{ProductSku, ProductStockAdjustment} 5 | import fmi.user.UserId 6 | 7 | import java.time.Instant 8 | import java.util.UUID 9 | 10 | case class Order(orderId: OrderId, user: UserId, orderLines: List[OrderLine], placingTimestamp: Instant) 11 | case class OrderId(id: String) extends AnyVal 12 | 13 | case class OrderLine(product: ProductSku, quantity: Int) { 14 | def toProductStockAdjustment = ProductStockAdjustment(product, -quantity) 15 | } 16 | 17 | object OrderId { 18 | def generate: IO[OrderId] = IO(OrderId(UUID.randomUUID().toString)) 19 | } 20 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/shopping/OrderDao.scala: -------------------------------------------------------------------------------- 1 | package fmi.shopping 2 | 3 | import cats.implicits._ 4 | import doobie._ 5 | import doobie.implicits._ 6 | import doobie.util.meta.LegacyInstantMetaInstance 7 | import fmi.infrastructure.db.DoobieDatabase.DbTransactor 8 | 9 | 10 | class OrderDao(dbTransactor: DbTransactor) extends LegacyInstantMetaInstance { 11 | def placeOrder(order: Order): ConnectionIO[Order] = { 12 | val insertOrder = sql""" 13 | INSERT INTO "order" (id, user_id, placing_timestamp) 14 | VALUES (${order.orderId}, ${order.user}, ${order.placingTimestamp}) 15 | """ 16 | 17 | val insertOrderLine = { 18 | val orderLinesInsert = """ 19 | INSERT INTO order_line(order_id, sku, quantity) 20 | VALUES(?, ?, ?) 21 | """ 22 | 23 | Update[(OrderId, OrderLine)](orderLinesInsert) 24 | .updateMany(order.orderLines.map(ol => (order.orderId, ol))) 25 | } 26 | 27 | (insertOrder.update.run *> insertOrderLine).as(order) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/shopping/OrderService.scala: -------------------------------------------------------------------------------- 1 | package fmi.shopping 2 | 3 | import cats.effect.IO 4 | import cats.implicits._ 5 | import doobie.implicits._ 6 | import fmi.infrastructure.db.DoobieDatabase.DbTransactor 7 | import fmi.inventory.{InventoryAdjustment, NotEnoughStockAvailable, NotEnoughStockAvailableException, ProductStockDao} 8 | import fmi.user.UserId 9 | 10 | class OrderService(dbTransactor: DbTransactor)(productStockDao: ProductStockDao, orderDao: OrderDao) { 11 | // TODO: validate shopping cart has positive quantities 12 | def placeOrder(user: UserId, shoppingCart: ShoppingCart): IO[Either[NotEnoughStockAvailable.type, Order]] = for { 13 | orderId <- OrderId.generate 14 | placingTimestamp <- IO.realTimeInstant 15 | 16 | order = Order(orderId, user, shoppingCart.orderLines, placingTimestamp) 17 | 18 | maybeOrder <- transactOrder(shoppingCart.toInventoryAdjustment, order) 19 | } yield maybeOrder 20 | 21 | private def transactOrder(inventoryAdjustment: InventoryAdjustment, order: Order) 22 | : IO[Either[NotEnoughStockAvailable.type, Order]] = { 23 | val transaction = 24 | productStockDao.applyInventoryAdjustmentAction(inventoryAdjustment) *> 25 | orderDao.placeOrder(order) 26 | 27 | transaction 28 | .transact(dbTransactor) 29 | .map(_.asRight[NotEnoughStockAvailable.type]) 30 | .recover { 31 | case NotEnoughStockAvailableException => NotEnoughStockAvailable.asLeft 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/shopping/ShippingRouter.scala: -------------------------------------------------------------------------------- 1 | package fmi.shopping 2 | 3 | import cats.effect.IO 4 | import fmi.user.AuthenticatedUser 5 | import io.circe.Codec 6 | import org.http4s.AuthedRoutes 7 | import org.http4s.dsl.io._ 8 | import cats.implicits._ 9 | import io.circe.generic.extras.semiauto.deriveUnwrappedCodec 10 | import io.circe.generic.semiauto.deriveCodec 11 | 12 | class ShippingRouter(orderService: OrderService) { 13 | import OrderJsonCodecs._ 14 | import org.http4s.circe.CirceEntityCodec._ 15 | 16 | def authenticatedRoutes: AuthedRoutes[AuthenticatedUser, IO] = AuthedRoutes.of[AuthenticatedUser, IO] { 17 | case authReq @ POST -> Root / "orders" as user => 18 | val placedOrder = authReq.req.as[ShoppingCart] >>= (orderService.placeOrder(user.id, _)) 19 | 20 | placedOrder.flatMap { 21 | _.fold(_ => Conflict(), Ok(_)) 22 | } 23 | } 24 | } 25 | 26 | object OrderJsonCodecs { 27 | import fmi.inventory.InventoryJsonCodecs.productSkuCodec 28 | import fmi.user.UsersJsonCodecs.userIdCodec 29 | 30 | implicit val orderLineCodec: Codec[OrderLine] = deriveCodec 31 | implicit val shoppingCartCodec: Codec[ShoppingCart] = deriveCodec 32 | 33 | implicit val orderIdCodec: Codec[OrderId] = deriveUnwrappedCodec 34 | implicit val orderCodec: Codec[Order] = deriveCodec 35 | } 36 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/shopping/ShoppingCart.scala: -------------------------------------------------------------------------------- 1 | package fmi.shopping 2 | 3 | import fmi.inventory.InventoryAdjustment 4 | 5 | case class ShoppingCart(orderLines: List[OrderLine] = List.empty) { 6 | def add(orderLine: OrderLine): ShoppingCart = { 7 | ShoppingCart(orderLine :: orderLines) 8 | } 9 | 10 | def toInventoryAdjustment: InventoryAdjustment = 11 | InventoryAdjustment(orderLines.map(_.toProductStockAdjustment)) 12 | } 13 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/shopping/ShoppingModule.scala: -------------------------------------------------------------------------------- 1 | package fmi.shopping 2 | 3 | import cats.effect.IO 4 | import cats.effect.kernel.Resource 5 | import fmi.infrastructure.db.DoobieDatabase.DbTransactor 6 | import fmi.inventory.ProductStockDao 7 | import fmi.user.AuthenticatedUser 8 | import org.http4s.AuthedRoutes 9 | 10 | case class ShoppingModule( 11 | orderDao: OrderDao, 12 | shippingRouter: OrderService, 13 | authenticatedRoutes: AuthedRoutes[AuthenticatedUser, IO] 14 | ) 15 | 16 | object ShoppingModule { 17 | def apply( 18 | dbTransactor: DbTransactor, 19 | productStockDao: ProductStockDao, 20 | ): Resource[IO, ShoppingModule] = { 21 | val orderDao = new OrderDao(dbTransactor) 22 | val orderService = new OrderService(dbTransactor)(productStockDao, orderDao) 23 | val shippingRouter = new ShippingRouter(orderService) 24 | 25 | Resource.pure( 26 | ShoppingModule(orderDao, orderService, shippingRouter.authenticatedRoutes) 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/user/AuthenticationUtils.scala: -------------------------------------------------------------------------------- 1 | package fmi.user 2 | 3 | import cats.data.{EitherT, Kleisli, OptionT} 4 | import cats.effect.IO 5 | import fmi.infrastructure.CryptoService 6 | import org.http4s.dsl.io._ 7 | import org.http4s.headers.Cookie 8 | import org.http4s.server.AuthMiddleware 9 | import org.http4s.{AuthedRoutes, Request, Response, ResponseCookie} 10 | 11 | case class AuthenticatedUser(id: UserId, role: UserRole.Value) 12 | 13 | class AuthenticationUtils(cryptoService: CryptoService, usersDao: UsersDao) { 14 | def responseWithUser(userId: UserId): IO[Response[IO]] = { 15 | val encryptedUser = cryptoService.encrypt(userId.email) 16 | Ok().map(_.addCookie(ResponseCookie("loggedUser", encryptedUser))) 17 | } 18 | 19 | def removeUser: IO[Response[IO]] = { 20 | Ok().map(_.removeCookie("loggedUser")) 21 | } 22 | 23 | private val authUser: Kleisli[IO, Request[IO], Either[String, AuthenticatedUser]] = Kleisli { request => 24 | val userId = for { 25 | header <- request.headers.get[Cookie].toRight("Cookie parsing error") 26 | cookie <- header.values.toList.find(_.name == "loggedUser").toRight("Not authenticated") 27 | email <- cryptoService.decrypt(cookie.content).toRight("Cookie invalid") 28 | } yield UserId(email) 29 | 30 | (for { 31 | userId <- EitherT.fromEither[IO](userId) 32 | user <- EitherT(usersDao.retrieveUser(userId).map(_.toRight("User not found"))) 33 | } yield AuthenticatedUser(user.id, user.role)).value 34 | } 35 | 36 | private val onFailure: AuthedRoutes[String, IO] = Kleisli(req => OptionT.liftF(Forbidden(req.context))) 37 | 38 | val authMiddleware: AuthMiddleware[IO, AuthenticatedUser] = AuthMiddleware(authUser, onFailure) 39 | } 40 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/user/PasswordUtils.scala: -------------------------------------------------------------------------------- 1 | package fmi.user 2 | 3 | import org.mindrot.jbcrypt.BCrypt 4 | 5 | object PasswordUtils { 6 | def hash(password: String): String = BCrypt.hashpw(password, BCrypt.gensalt()) 7 | def checkPasswords(password: String, passwordHash: String): Boolean = BCrypt.checkpw(password, passwordHash) 8 | } 9 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/user/User.scala: -------------------------------------------------------------------------------- 1 | package fmi.user 2 | 3 | case class User( 4 | id: UserId, 5 | passwordHash: String, 6 | role: UserRole.Value, 7 | name: String, 8 | age: Option[Int] 9 | ) 10 | 11 | case class UserId(email: String) extends AnyVal 12 | 13 | object UserRole extends Enumeration { 14 | val Admin, NormalUser = Value 15 | } 16 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/user/UserRegistrationForm.scala: -------------------------------------------------------------------------------- 1 | package fmi.user 2 | 3 | import cats.data.EitherNec 4 | import cats.syntax.either._ 5 | import cats.syntax.parallel._ 6 | import cats.syntax.traverse._ 7 | 8 | case class UserRegistrationForm(email: String, password: String, name: String, age: Option[Int]) 9 | 10 | sealed trait RegistrationFormError 11 | case class InvalidEmail(email: String) extends RegistrationFormError 12 | case object NameIsEmpty extends RegistrationFormError 13 | case class InvalidAge(age: Int) extends RegistrationFormError 14 | 15 | object UserRegistrationForm { 16 | def validate(userRegistrationForm: UserRegistrationForm): EitherNec[RegistrationFormError, User] = { 17 | ( 18 | validateEmail(userRegistrationForm.email), 19 | PasswordUtils.hash(userRegistrationForm.password).rightNec, 20 | UserRole.NormalUser.rightNec, 21 | validateName(userRegistrationForm.name), 22 | validateAge(userRegistrationForm.age) 23 | ).parMapN(User.apply) 24 | } 25 | 26 | def validateEmail(email: String): EitherNec[RegistrationFormError, UserId] = { 27 | if (email.count(_ == '@') == 1) UserId(email).rightNec 28 | else InvalidEmail(email).leftNec 29 | } 30 | 31 | def validateName(name: String): EitherNec[RegistrationFormError, String] = { 32 | if (name.nonEmpty) name.rightNec 33 | else NameIsEmpty.leftNec 34 | } 35 | 36 | def validateAge(maybeAge: Option[Int]): EitherNec[RegistrationFormError, Option[Int]] = maybeAge.map { age => 37 | if (age > 0) age.rightNec 38 | else InvalidAge(age).leftNec 39 | }.sequence 40 | } 41 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/user/UsersDao.scala: -------------------------------------------------------------------------------- 1 | package fmi.user 2 | 3 | import cats.effect.IO 4 | import fmi.infrastructure.db.DoobieDatabase.DbTransactor 5 | import cats.syntax.functor._ 6 | import doobie._ 7 | import doobie.implicits._ 8 | import doobie.postgres.sqlstate 9 | 10 | object UserDbImplicits { 11 | implicit val userRoleMeta = Meta[String].imap(UserRole.withName)(_.toString) 12 | } 13 | 14 | class UsersDao(dbTransactor: DbTransactor) { 15 | import UserDbImplicits._ 16 | 17 | def retrieveUser(id: UserId): IO[Option[User]] = { 18 | sql""" 19 | SELECT email, password_hash, role, name, age 20 | FROM "user" 21 | WHERE email = $id 22 | """ 23 | .query[User] 24 | .option 25 | .transact(dbTransactor) 26 | } 27 | 28 | def registerUser(user: User): IO[Either[UserAlreadyExists, User]] = { 29 | sql""" 30 | INSERT INTO "user" (email, password_hash, role, name, age) 31 | VALUES (${user.id}, ${user.passwordHash}, ${user.role}, ${user.name}, ${user.age}) 32 | """ 33 | .update 34 | .run 35 | .as(user) 36 | .attemptSomeSqlState { 37 | case sqlstate.class23.UNIQUE_VIOLATION => UserAlreadyExists(user.id) 38 | } 39 | .transact(dbTransactor) 40 | } 41 | 42 | def deleteUser(id: UserId): IO[Unit] = { 43 | sql""" 44 | DELETE FROM user 45 | WHERE email = $id 46 | """ 47 | .update 48 | .run 49 | .void 50 | .transact(dbTransactor) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/user/UsersModule.scala: -------------------------------------------------------------------------------- 1 | package fmi.user 2 | 3 | import cats.effect.IO 4 | import cats.effect.kernel.Resource 5 | import fmi.infrastructure.CryptoService 6 | import fmi.infrastructure.db.DoobieDatabase.DbTransactor 7 | import org.http4s.{AuthedRoutes, HttpRoutes} 8 | import org.http4s.server.AuthMiddleware 9 | 10 | case class UsersModule( 11 | usersDao: UsersDao, 12 | usersService: UsersService, 13 | authMiddleware: AuthMiddleware[IO, AuthenticatedUser], 14 | routes: HttpRoutes[IO], 15 | authenticatedRoutes: AuthedRoutes[AuthenticatedUser, IO] 16 | ) 17 | 18 | object UsersModule { 19 | def apply(dbTransactor: DbTransactor, cryptoService: CryptoService): Resource[IO, UsersModule] = { 20 | val usersDao = new UsersDao(dbTransactor) 21 | val usersService = new UsersService(usersDao) 22 | val authenticationUtils = new AuthenticationUtils(cryptoService, usersDao) 23 | val usersRouter = new UsersRouter(usersService, authenticationUtils) 24 | 25 | Resource.pure(UsersModule( 26 | usersDao, 27 | usersService, 28 | authenticationUtils.authMiddleware, 29 | usersRouter.nonAuthenticatedRoutes, 30 | usersRouter.authenticatedRoutes 31 | )) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/user/UsersService.scala: -------------------------------------------------------------------------------- 1 | package fmi.user 2 | 3 | import cats.data.{EitherT, NonEmptyChain} 4 | import cats.effect.IO 5 | import cats.implicits._ 6 | 7 | class UsersService(usersDao: UsersDao) { 8 | def registerUser(registrationForm: UserRegistrationForm): IO[Either[RegistrationError, User]] = (for { 9 | user <- EitherT.fromEither[IO]( 10 | UserRegistrationForm 11 | .validate(registrationForm) 12 | .leftMap(UserValidationError) 13 | .leftWiden[RegistrationError] 14 | ) 15 | _ <- EitherT(usersDao.registerUser(user)).leftWiden[RegistrationError] 16 | } yield user).value 17 | 18 | def login(userLogin: UserLogin): IO[Option[User]] = { 19 | usersDao.retrieveUser(userLogin.email).map { 20 | case Some(user) => 21 | if (PasswordUtils.checkPasswords(userLogin.password, user.passwordHash)) Some(user) 22 | else None 23 | case _ => None 24 | } 25 | } 26 | } 27 | 28 | sealed trait RegistrationError 29 | case class UserValidationError(registrationErrors: NonEmptyChain[RegistrationFormError]) extends RegistrationError 30 | case class UserAlreadyExists(email: UserId) extends RegistrationError 31 | 32 | case class UserLogin(email: UserId, password: String) -------------------------------------------------------------------------------- /lectures/examples/xx-akka-examples/build.sbt: -------------------------------------------------------------------------------- 1 | name := "akka-examples" 2 | version := "0.1" 3 | 4 | scalaVersion := "2.13.6" 5 | 6 | val akkaVersion = "2.6.13" 7 | 8 | libraryDependencies ++= Seq( 9 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 10 | "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-stream-typed" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-http" % "10.2.4", 13 | 14 | "ch.qos.logback" % "logback-classic" % "1.2.3", 15 | 16 | "org.scalatest" %% "scalatest" % "3.2.5" % Test 17 | ) 18 | -------------------------------------------------------------------------------- /lectures/examples/xx-akka-examples/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.1 2 | -------------------------------------------------------------------------------- /lectures/examples/xx-akka-examples/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | true 9 | 10 | [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /lectures/examples/xx-akka-examples/src/main/scala/actors/ActorsExample1.scala: -------------------------------------------------------------------------------- 1 | package actors 2 | 3 | import akka.actor.{Actor, ActorSystem, Props} 4 | 5 | class Human extends Actor { 6 | def receive: Receive = { 7 | case "Hello" => 8 | println("Hello, how are you?") 9 | } 10 | } 11 | 12 | object ActorsExample1 extends App { 13 | val actorSystem = ActorSystem() 14 | 15 | val george = actorSystem.actorOf(Props[Human]()) 16 | 17 | george ! "Hello" 18 | } 19 | -------------------------------------------------------------------------------- /lectures/examples/xx-akka-examples/src/main/scala/actors/ActorsExample2.scala: -------------------------------------------------------------------------------- 1 | package actors 2 | 3 | import akka.actor.{Actor, ActorRef, ActorSystem, Props} 4 | 5 | object Counter { 6 | trait CounterProtocol 7 | case class IncreaseCountWith(value: Int) extends CounterProtocol 8 | case object GetCount extends CounterProtocol 9 | 10 | case class Count(value: Int, numberOfIncreases: Int) extends CounterProtocol 11 | } 12 | 13 | class Counter extends Actor { 14 | import Counter._ 15 | 16 | def receive: Receive = countBehaviour(0, 0) 17 | 18 | def countBehaviour(currentCount: Int, numberOfIncreases: Int): Receive = { 19 | case IncreaseCountWith(n) => 20 | context.become(countBehaviour(currentCount + n, numberOfIncreases + 1)) 21 | case GetCount => 22 | sender() ! Count(currentCount, numberOfIncreases) 23 | } 24 | } 25 | 26 | class Worker(counter: ActorRef) extends Actor { 27 | import Counter._ 28 | 29 | def receive: Receive = { 30 | case "Start" => 31 | println("Starting work...") 32 | 33 | counter ! IncreaseCountWith(1) 34 | counter ! IncreaseCountWith(2) 35 | counter ! IncreaseCountWith(3) 36 | counter ! GetCount 37 | counter ! IncreaseCountWith(4) 38 | counter ! IncreaseCountWith(5) 39 | counter ! IncreaseCountWith(6) 40 | counter ! GetCount 41 | 42 | println("Work finished") 43 | 44 | case Count(n, times) => 45 | println(s"Current counter is $n, has been increased $times times") 46 | } 47 | } 48 | 49 | object ActorsExample2 extends App { 50 | val actorSystem = ActorSystem() 51 | 52 | val counter = actorSystem.actorOf(Props[Counter]()) 53 | val worker = actorSystem.actorOf(Props(new Worker(counter))) 54 | 55 | worker ! "Start" 56 | } -------------------------------------------------------------------------------- /lectures/examples/xx-akka-examples/src/main/scala/actors/ActorsExample3.scala: -------------------------------------------------------------------------------- 1 | package actors 2 | 3 | import actors.Person.Greet 4 | import akka.actor.{Actor, ActorRef, ActorSystem, Props} 5 | 6 | object Person { 7 | sealed trait PersonProtocol 8 | case class Greet(person: ActorRef) extends PersonProtocol 9 | case object Hello extends PersonProtocol 10 | case object WhatsYourName extends PersonProtocol 11 | case class NameReply(name: String) extends PersonProtocol 12 | 13 | def props(name: String) = Props(new Person(name)) 14 | } 15 | 16 | class Person(name: String) extends Actor { 17 | import Person._ 18 | 19 | def receive: Receive = start 20 | 21 | def start: Receive = { 22 | case Greet(person) => 23 | person ! Hello 24 | 25 | context.become(sentGreetings(person)) 26 | case Hello => 27 | println(s"$name was greeted") 28 | 29 | sender() ! Hello 30 | 31 | context.become(greeted(sender())) 32 | } 33 | 34 | def sentGreetings(to: ActorRef): Receive = { 35 | case Hello => 36 | println(s"$name was greeted") 37 | 38 | sender() ! WhatsYourName 39 | 40 | case NameReply(otherPersonName) => 41 | if (sender() == to) 42 | println(s"$name received other person name: $otherPersonName") 43 | else 44 | println(s"A stranger introduced themselves to me ($name)") 45 | 46 | context.become(start) 47 | } 48 | 49 | def greeted(by: ActorRef): Receive = { 50 | case WhatsYourName => 51 | if (sender() == by) { 52 | println(s"$name was asked for name") 53 | 54 | sender() ! NameReply(name) 55 | } else println(s"$name asked for name by a stranger") 56 | 57 | context.become(start) 58 | } 59 | } 60 | 61 | object ActorsExample extends App { 62 | val actorSystem = ActorSystem() 63 | 64 | val george = actorSystem.actorOf(Person.props("George")) 65 | val john = actorSystem.actorOf(Person.props("John")) 66 | 67 | george ! Greet(john) 68 | } 69 | -------------------------------------------------------------------------------- /lectures/generate-presentation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$#" -ne 1 ]; then 4 | echo "Usage: ./generate-presentation.sh " 5 | exit 6 | fi 7 | 8 | lecture_file="$1" 9 | lecture="${lecture_file%.*}" 10 | 11 | pandoc -t revealjs \ 12 | -s \ 13 | -o "$lecture".html \ 14 | "$lecture_file" \ 15 | -V revealjs-url=reveal-js \ 16 | -V theme=white \ 17 | --css=theme/theme.css \ 18 | -V transition=fade \ 19 | -V center=false 20 | -------------------------------------------------------------------------------- /lectures/images/01/case-ended.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/case-ended.webp -------------------------------------------------------------------------------- /lectures/images/01/crown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/crown.jpg -------------------------------------------------------------------------------- /lectures/images/01/grammar-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/grammar-size.png -------------------------------------------------------------------------------- /lectures/images/01/greybeard.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/greybeard.jpeg -------------------------------------------------------------------------------- /lectures/images/01/lonely-island.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/lonely-island.gif -------------------------------------------------------------------------------- /lectures/images/01/milewski.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/milewski.jpg -------------------------------------------------------------------------------- /lectures/images/01/netflix-scala.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/netflix-scala.jpg -------------------------------------------------------------------------------- /lectures/images/01/ocado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/ocado.png -------------------------------------------------------------------------------- /lectures/images/01/odersky.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/odersky.JPG -------------------------------------------------------------------------------- /lectures/images/01/pretending-to-write.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/pretending-to-write.gif -------------------------------------------------------------------------------- /lectures/images/01/scala-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/scala-logo.png -------------------------------------------------------------------------------- /lectures/images/01/scala3-logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/scala3-logo.jpeg -------------------------------------------------------------------------------- /lectures/images/01/scala3-rc1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/scala3-rc1.png -------------------------------------------------------------------------------- /lectures/images/01/static-type-system.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/01/static-type-system.jpeg -------------------------------------------------------------------------------- /lectures/images/02-fp-with-scala/functional-wizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/02-fp-with-scala/functional-wizard.png -------------------------------------------------------------------------------- /lectures/images/02-fp-with-scala/functions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/02-fp-with-scala/functions.png -------------------------------------------------------------------------------- /lectures/images/02-fp-with-scala/java-memory-model-multithreaded.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/02-fp-with-scala/java-memory-model-multithreaded.jpg -------------------------------------------------------------------------------- /lectures/images/02-fp-with-scala/java-memory-model.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/02-fp-with-scala/java-memory-model.jpg -------------------------------------------------------------------------------- /lectures/images/02-fp-with-scala/primitive-and-referenced-types.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/02-fp-with-scala/primitive-and-referenced-types.jpg -------------------------------------------------------------------------------- /lectures/images/03-oop-in-a-functional-language/alan-kay-raising-hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/03-oop-in-a-functional-language/alan-kay-raising-hand.png -------------------------------------------------------------------------------- /lectures/images/03-oop-in-a-functional-language/alan-kay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/03-oop-in-a-functional-language/alan-kay.jpg -------------------------------------------------------------------------------- /lectures/images/03-oop-in-a-functional-language/covid-vaccine.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/03-oop-in-a-functional-language/covid-vaccine.webp -------------------------------------------------------------------------------- /lectures/images/03-oop-in-a-functional-language/messaging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/03-oop-in-a-functional-language/messaging.png -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/building-blocks.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/building-blocks.webp -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/captain-obvious.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/captain-obvious.jpg -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/chicken-curry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/chicken-curry.jpg -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/filter.png -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/lego-blocks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/lego-blocks.jpg -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/list-append.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/list-append.jpg -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/list.jpg -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/map.png -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/multple-lists.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/multple-lists.jpg -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/reduce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/reduce.png -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/shared-objects.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/shared-objects.jpg -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/stack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/stack.jpg -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/vector-update.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/vector-update.jpg -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/vector.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/04-key-fp-approaches/vector.jpg -------------------------------------------------------------------------------- /lectures/images/08-concurrency/cpu-cache.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/08-concurrency/cpu-cache.jpeg -------------------------------------------------------------------------------- /lectures/images/08-concurrency/dijkstra.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/08-concurrency/dijkstra.jpg -------------------------------------------------------------------------------- /lectures/images/09-type-classes/category-theory-for-programmers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/09-type-classes/category-theory-for-programmers.png -------------------------------------------------------------------------------- /lectures/images/09-type-classes/cats-cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/09-type-classes/cats-cat.png -------------------------------------------------------------------------------- /lectures/images/09-type-classes/cats-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/09-type-classes/cats-small.png -------------------------------------------------------------------------------- /lectures/images/09-type-classes/cats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/09-type-classes/cats.png -------------------------------------------------------------------------------- /lectures/images/09-type-classes/scala-with-cats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/09-type-classes/scala-with-cats.png -------------------------------------------------------------------------------- /lectures/images/09-type-classes/vivian-boyan-cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/09-type-classes/vivian-boyan-cat.jpg -------------------------------------------------------------------------------- /lectures/images/10-monads-and-applicatives/big-cat-burrito.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/10-monads-and-applicatives/big-cat-burrito.jpeg -------------------------------------------------------------------------------- /lectures/images/10-monads-and-applicatives/category-theory-for-programmers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/10-monads-and-applicatives/category-theory-for-programmers.png -------------------------------------------------------------------------------- /lectures/images/10-monads-and-applicatives/functional-programming-in-scala.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/10-monads-and-applicatives/functional-programming-in-scala.jpeg -------------------------------------------------------------------------------- /lectures/images/10-monads-and-applicatives/impure-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/10-monads-and-applicatives/impure-logo.png -------------------------------------------------------------------------------- /lectures/images/11-cats-and-cats-effects/hierarchy-impure.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/11-cats-and-cats-effects/hierarchy-impure.jpeg -------------------------------------------------------------------------------- /lectures/images/12-building-a-scala-app/circe-json-data-types.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/12-building-a-scala-app/circe-json-data-types.jpeg -------------------------------------------------------------------------------- /lectures/images/Left-fold-transformation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/Left-fold-transformation.png -------------------------------------------------------------------------------- /lectures/images/Right-fold-transformation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/Right-fold-transformation.png -------------------------------------------------------------------------------- /lectures/images/actor_top_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/actor_top_tree.png -------------------------------------------------------------------------------- /lectures/images/akka_full_color.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lectures/images/animation-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/animation-reverse.gif -------------------------------------------------------------------------------- /lectures/images/arch_tree_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/arch_tree_diagram.png -------------------------------------------------------------------------------- /lectures/images/but-why.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/but-why.gif -------------------------------------------------------------------------------- /lectures/images/carl-hewitt.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/carl-hewitt.jpeg -------------------------------------------------------------------------------- /lectures/images/classhierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/classhierarchy.png -------------------------------------------------------------------------------- /lectures/images/godji-opakovka2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/godji-opakovka2.jpg -------------------------------------------------------------------------------- /lectures/images/hello-it.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/hello-it.jpeg -------------------------------------------------------------------------------- /lectures/images/joe-armstrong-quote.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/joe-armstrong-quote.jpg -------------------------------------------------------------------------------- /lectures/images/joe-armstrong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/joe-armstrong.png -------------------------------------------------------------------------------- /lectures/images/reduce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/reduce.png -------------------------------------------------------------------------------- /lectures/images/rob_norris.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/rob_norris.jpg -------------------------------------------------------------------------------- /lectures/images/scala3-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/scala3-small.png -------------------------------------------------------------------------------- /lectures/images/scala3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/scala3.png -------------------------------------------------------------------------------- /lectures/images/tpolecat_facepalm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/tpolecat_facepalm.jpg -------------------------------------------------------------------------------- /lectures/images/whaaat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/whaaat.webp -------------------------------------------------------------------------------- /lectures/images/work.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/work.png -------------------------------------------------------------------------------- /lectures/images/zipper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2021/97872bc612cc90e42541056434ffedea7938a5ad/lectures/images/zipper.png -------------------------------------------------------------------------------- /lectures/theme/theme.css: -------------------------------------------------------------------------------- 1 | .reveal { 2 | font-size: 28px; 3 | } 4 | 5 | .reveal h1 { 6 | font-size: 48px; 7 | } 8 | 9 | .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 { 10 | text-transform: none; 11 | } 12 | 13 | .reveal .center-cells table th, 14 | .reveal .center-cells table td { 15 | text-align: center; 16 | } 17 | 18 | .reveal section img { 19 | border: none; 20 | } 21 | 22 | .reveal .align { 23 | text-align: left; 24 | } 25 | 26 | .reveal li pre { 27 | width: auto; 28 | } 29 | 30 | .slide-background.scala3::before { 31 | display: block; 32 | position: absolute; 33 | top: 0; 34 | left: 32px; 35 | content: url(../images/scala3-small.png); 36 | width: 146px; 37 | height: 236px; 38 | box-shadow: 32px 32px 100px; 39 | } 40 | 41 | p > code, li > code { 42 | white-space: pre-wrap; 43 | background: #f0f0f0; 44 | padding: 0 2px; 45 | border-radius: 4px; 46 | } 47 | 48 | .reveal li p { 49 | margin: 0; 50 | } 51 | 52 | .reveal div.sourceCode { 53 | margin: 0.5em 0; 54 | } 55 | 56 | .reveal li pre.sourceCode { 57 | margin: 8px; 58 | } 59 | 60 | .reveal pre code { 61 | max-height: 520px; 62 | } 63 | 64 | .reveal blockquote { 65 | box-sizing: border-box; 66 | width: 80%; 67 | margin: 16px auto; 68 | background: #334455; 69 | padding: 5px; 70 | color: white; 71 | font-weight: bold; 72 | font-style: italic; 73 | border-radius: 10px; 74 | } 75 | --------------------------------------------------------------------------------