├── .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 ├── 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-alt.html ├── 10-monads-and-applicatives-alt.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 │ │ ├── build.sbt │ │ ├── project │ │ │ └── build.properties │ │ └── src │ │ │ ├── main │ │ │ └── scala │ │ │ │ ├── HelloWorld.scala │ │ │ │ └── com │ │ │ │ └── scalafmi │ │ │ │ └── exercises.scala │ │ │ └── test │ │ │ └── scala │ │ │ └── ExampleSpec.scala │ ├── 03-oop-in-a-functional-language │ │ ├── build.sbt │ │ ├── project │ │ │ └── build.properties │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ ├── expressionproblem │ │ │ ├── fp │ │ │ │ └── Shape.scala │ │ │ └── oop │ │ │ │ └── Shape.scala │ │ │ ├── mathematical │ │ │ └── Rational.scala │ │ │ └── scalafmi │ │ │ └── numberextensions │ │ │ ├── ExtensionMethodsExamples.scala │ │ │ └── NumberExtensions.scala │ ├── 04-key-fp-approaches │ │ ├── build.sbt │ │ ├── project │ │ │ └── build.properties │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ ├── answers │ │ │ ├── HigherOrderFunctions.scala │ │ │ ├── Recursion.scala │ │ │ └── multiparameters │ │ │ │ ├── GroupingOfParameters.scala │ │ │ │ ├── LanguageConstructs.scala │ │ │ │ └── TypeInference.scala │ │ │ └── exercises │ │ │ ├── HigherOrderFunctions.scala │ │ │ ├── MultiParameterLists.scala │ │ │ └── Recursion.scala │ ├── 08-concurrency │ │ ├── .scalafmt.conf │ │ ├── build.sbt │ │ ├── project │ │ │ ├── build.properties │ │ │ └── plugins.sbt │ │ └── src │ │ │ └── main │ │ │ ├── resources │ │ │ └── logback.xml │ │ │ └── scala │ │ │ ├── callbacks │ │ │ └── Callbacks.scala │ │ │ ├── concurrent │ │ │ ├── ExecutionContexts.scala │ │ │ ├── Executors.scala │ │ │ ├── future │ │ │ │ ├── Future.scala │ │ │ │ ├── Promise.scala │ │ │ │ └── PromiseAlternativeImplementation.scala │ │ │ ├── io │ │ │ │ └── IO.scala │ │ │ └── lecture │ │ │ │ └── Future.scala │ │ │ ├── console │ │ │ ├── Console.scala │ │ │ └── ConsoleForIO.scala │ │ │ ├── http │ │ │ ├── FutureWebServer.scala │ │ │ ├── HttpClient.scala │ │ │ ├── HttpRequests.scala │ │ │ ├── LibraryClient.scala │ │ │ └── LibraryWebServer.scala │ │ │ ├── library │ │ │ └── Library.scala │ │ │ ├── product │ │ │ └── Product.scala │ │ │ ├── referentialtransparancy │ │ │ ├── FutureReferentialTransparencyExample1.scala │ │ │ └── FutureReferentialTransparencyExample2.scala │ │ │ ├── threads │ │ │ ├── ThreadPoolsExample.scala │ │ │ ├── ThreadsExample.scala │ │ │ ├── ThreadsSharingData.scala │ │ │ └── ThreadsSharingData2.scala │ │ │ └── util │ │ │ ├── HttpServiceUrls.scala │ │ │ └── Utils.scala │ ├── 09-type-classes-scala-2 │ │ ├── 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 │ ├── 09-type-classes │ │ ├── .scalafmt.conf │ │ ├── build.sbt │ │ ├── other │ │ │ ├── monoid.hs │ │ │ ├── monoid.rs │ │ │ └── shapes.rs │ │ ├── project │ │ │ ├── build.properties │ │ │ └── plugins.sbt │ │ └── 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 │ ├── 10-monads-and-applicatives │ │ ├── .scalafmt.conf │ │ ├── build.sbt │ │ ├── project │ │ │ ├── build.properties │ │ │ └── plugins.sbt │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ ├── effects │ │ │ ├── Applicative.scala │ │ │ ├── Functor.scala │ │ │ ├── Monad.scala │ │ │ ├── Traversable.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 │ │ ├── .scalafmt.conf │ │ ├── build.sbt │ │ ├── project │ │ │ ├── build.properties │ │ │ └── plugins.sbt │ │ └── src │ │ │ ├── main │ │ │ └── scala │ │ │ │ ├── cats │ │ │ │ ├── Ex10ConcurrentParallelDemo.scala │ │ │ │ ├── Ex1DataTypesSyntax.scala │ │ │ │ ├── Ex2EqDemo.scala │ │ │ │ ├── Ex3MonoidDemo.scala │ │ │ │ ├── Ex4FoldableDemo.scala │ │ │ │ ├── Ex5FunctorDemo.scala │ │ │ │ ├── Ex6ApplyApplicativeTraverseDemo.scala │ │ │ │ ├── Ex7FlatMapMonadMonadErrorDemo.scala │ │ │ │ ├── Ex8Composition.scala │ │ │ │ └── Ex9ParallelDemo.scala │ │ │ │ ├── io │ │ │ │ ├── Ex01RunningIO.scala │ │ │ │ ├── Ex02IOApp.scala │ │ │ │ ├── Ex03Fibers.scala │ │ │ │ ├── Ex04Cancellation.scala │ │ │ │ ├── Ex05Resource.scala │ │ │ │ ├── Ex06SharedConcurrentAccess.scala │ │ │ │ └── Ex7Refs.scala │ │ │ │ ├── math │ │ │ │ └── Rational.scala │ │ │ │ └── user │ │ │ │ └── UserRegistration.scala │ │ │ └── test │ │ │ └── scala │ │ │ ├── io │ │ │ └── ChannelTestSuite.scala │ │ │ └── math │ │ │ └── RationalMonoidTest.scala │ ├── 12-building-a-scala-app │ │ ├── .scalafmt.conf │ │ ├── build.sbt │ │ ├── celsius.txt │ │ ├── fahrenheit.txt │ │ ├── project │ │ │ ├── build.properties │ │ │ └── plugins.sbt │ │ └── 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 │ │ │ │ ├── json │ │ │ │ ├── examples │ │ │ │ │ └── IdCard.scala │ │ │ │ └── semiauto │ │ │ │ │ └── Tweet.scala │ │ │ │ ├── modularitycomposition │ │ │ │ ├── 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 │ │ │ │ ├── modularitythincake │ │ │ │ ├── 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 │ ├── 12-library-app │ │ ├── .scalafmt.conf │ │ ├── build.sbt │ │ ├── project │ │ │ ├── build.properties │ │ │ └── plugins.sbt │ │ └── 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 │ │ ├── .scalafmt.conf │ │ ├── README.md │ │ ├── build.sbt │ │ ├── project │ │ ├── build.properties │ │ └── plugins.sbt │ │ └── 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 │ │ └── utils │ │ └── CirceUtils.scala ├── generate-presentation.sh ├── highlight-scala.xml ├── images │ ├── 01-intro │ │ ├── boyan.jpg │ │ ├── case-ended.webp │ │ ├── cheering-minions.gif │ │ ├── dany.jpg │ │ ├── grammar-size.png │ │ ├── odersky.JPG │ │ ├── pretending-to-write.gif │ │ ├── static-type-system.jpeg │ │ ├── vassil.jpg │ │ ├── viktor.jpg │ │ └── zdravko.jpg │ ├── 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 │ │ ├── git-objects-1.png │ │ ├── git-objects-2.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 │ │ ├── Screenshot_20220518_173430.png │ │ ├── Screenshot_20220518_173528.png │ │ ├── cats-effect.png │ │ ├── hierarchy-impure.jpeg │ │ └── typelevel.svg │ ├── 12-building-a-scala-app │ │ └── circe-json-data-types.jpeg │ ├── Left-fold-transformation.png │ ├── Right-fold-transformation.png │ ├── animation-reverse.gif │ ├── but-why.gif │ ├── classhierarchy.png │ ├── collections-diagram-213.svg │ ├── godji-opakovka2.jpg │ ├── reduce.png │ ├── scala-logo.png │ ├── scala3-small.png │ ├── scala3.png │ ├── whaaat.webp │ ├── work.png │ └── zipper.png └── theme │ └── theme.css ├── project-instructions.md └── resources ├── cats-cheat-sheet.md ├── pattern-matching.md ├── type-elements-in-scala.md └── variance.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | 4 | .bsp 5 | target 6 | metals.sbt 7 | .metals 8 | .bloop 9 | .vscode 10 | 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lectures/reveal-js"] 2 | path = lectures/reveal-js 3 | url = https://github.com/hakimel/reveal.js.git 4 | -------------------------------------------------------------------------------- /lectures/10-monads-and-applicatives-alt.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: I got you, FAM! or Functor / Applicative / Monad 3 | --- 4 | 5 | # The story so far 6 | 7 | :::incremental 8 | 9 | * What are type classes? 10 | * Build a set of rules a type. 11 | * Enable ad-hoc polymorphism 12 | * In Scala: Context aware & able to augment existing types 13 | 14 | ::: 15 | 16 | # A brief detour 17 | 18 | Having a hierarchy C extends B extends A which one is the most: 19 | 20 | * powerful 21 | * the highest abstraction 22 | 23 |

24 | > Rule Of Least Power 25 | - When designing computer systems, one is often faced with a choice between using a more or less powerful language for publishing information, for expressing constraints, or for solving some problem. This finding explores tradeoffs relating the choice of language to reusability of information. The "Rule of Least Power" suggests choosing the least powerful language suitable for a given purpose. 26 |

27 | 28 | 29 | # Functional Programming in Scala 30 | 31 | [![](images/10-monads-and-applicatives/functional-programming-in-scala.jpeg){ height="520" }](https://www.manning.com/books/functional-programming-in-scala) 32 | 33 | # Теория на категориите 34 | 35 | [![](images/10-monads-and-applicatives/category-theory-for-programmers.png){ height="520" }](https://github.com/hmemcpy/milewski-ctfp-pdf) 36 | -------------------------------------------------------------------------------- /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/build.sbt: -------------------------------------------------------------------------------- 1 | name := "hello-world" 2 | version := "0.1" 3 | 4 | scalaVersion := "3.1.1" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.scalatest" %% "scalatest" % "3.2.11" % Test 8 | ) 9 | 10 | scalacOptions ++= Seq( 11 | "-new-syntax", 12 | "-indent" 13 | ) 14 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/src/main/scala/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | @main def hello = println("Hello, World!") 2 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/src/main/scala/com/scalafmi/exercises.scala: -------------------------------------------------------------------------------- 1 | package com.scalafmi 2 | 3 | def toInteger(value: Any): Int = 4 | value match 5 | case n: Int => n 6 | case s: String => s.toInt 7 | case d: Double => d.toInt 8 | 9 | 10 | @main def run = 11 | toInteger(42) // 42 12 | toInteger("42") // 42 13 | toInteger(42.0) // 42 14 | toInteger(List.empty) 15 | 16 | val parsedResult = 17 | try 18 | val string = "42L" 19 | string.toInt 20 | catch 21 | case e: NumberFormatException => 0 22 | 23 | def sum(xs: Seq[Int]): Int = 24 | if xs.isEmpty then 0 25 | else xs.head + sum(xs.tail) 26 | 27 | def balanced(e: List[Char]): Boolean = 28 | def weight(char: Char): Int = char match 29 | case '(' => 1 30 | case ')' => -1 31 | case _ => 0 32 | 33 | def calculate(e: List[Char], balance: Int): Int = 34 | if e.isEmpty then balance 35 | else if balance < 0 then balance 36 | else calculate(e.tail, balance + weight(e.head)) 37 | 38 | calculate(e, 0) == 0 39 | 40 | 41 | @main def runBalance = println { 42 | balanced("((1 + 2) * 3".toList) 43 | } 44 | 45 | def example = 46 | lazy val a = 10 47 | 48 | lazy val b = 40 49 | lazy val c = 50 50 | 51 | lazy val y = b * 40 52 | 53 | lazy val x = a + c 54 | lazy val z = y * x * x 55 | 56 | def substrings(str: String): Seq[String] = 57 | for 58 | beginningIndex <- 0 until str.size 59 | length <- 1 to str.size - beginningIndex 60 | yield str.drop(beginningIndex).take(length) 61 | 62 | @main def runSubstrings = println { 63 | substrings("abcdef") 64 | } 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /lectures/examples/02-fp-with-scala/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 shouldBe 5 7 | } -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/build.sbt: -------------------------------------------------------------------------------- 1 | name := "oop-in-a-functional-language" 2 | version := "0.1" 3 | 4 | scalaVersion := "3.1.1" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.scalatest" %% "scalatest" % "3.2.11" % Test 8 | ) 9 | 10 | scalacOptions ++= Seq( 11 | "-new-syntax", 12 | "-indent" 13 | ) 14 | -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 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 | case class Square(a: Double) extends Shape 7 | 8 | def area(s: Shape): Double = s match 9 | case Circle(r) => math.Pi * r * r 10 | case Rectangle(a, b) => a * b 11 | case Square(a) => a * a 12 | 13 | 14 | def circumference(s: Shape): Double = s match 15 | case Circle(r) => 2 * math.Pi * r 16 | case Rectangle(a, b) => 2 * (a + b) 17 | case Square(a) => 4 * a 18 | -------------------------------------------------------------------------------- /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 | def circumference: Double 6 | 7 | case class Circle(r: Double) extends Shape: 8 | def area: Double = math.Pi * r * r 9 | def circumference: Double = 2 * math.Pi * r 10 | 11 | case class Rectangle(a: Double, b: Double) extends Shape: 12 | def area: Double = a * b 13 | def circumference: Double = 2 * (a + b) 14 | 15 | case class Square(a: Double) extends Shape: 16 | def area: Double = a * a 17 | def circumference: Double = 4 * a 18 | -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/src/main/scala/mathematical/Rational.scala: -------------------------------------------------------------------------------- 1 | package mathematical 2 | 3 | import scala.language.implicitConversions 4 | 5 | class Rational(n: Int, d: Int = 1) extends Ordered[Rational]: 6 | require(d != 0) 7 | 8 | val (numer, denom) = 9 | val div = gcd(n, d) 10 | ((n / div) * d.sign, (d / div).abs) 11 | 12 | def compare(that: Rational): Int = (this - that).numer 13 | 14 | def unary_- = Rational(-numer, denom) 15 | 16 | def unary_~ = Rational(denom, numer) 17 | 18 | def +(that: Rational) = Rational( 19 | numer * that.denom + that.numer * denom, 20 | denom * that.denom 21 | ) 22 | 23 | def -(that: Rational) = this + (-that) 24 | 25 | def *(that: Rational) = Rational(numer * that.numer, denom * that.denom) 26 | 27 | def /(that: Rational) = this * (~that) 28 | 29 | override def toString: String = s"$numer/$denom" 30 | 31 | override def hashCode(): Int = (numer, denom).## 32 | 33 | override def equals(obj: Any): Boolean = obj match 34 | case that: Rational => numer == that.numer && denom == that.denom 35 | case _ => false 36 | 37 | private def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a % b) 38 | 39 | object Rational: 40 | val Zero = Rational(0) // използва apply, дефиниран долу 41 | 42 | // def apply(n: Int, d: Int = 1) = new Rational(n, d) 43 | 44 | implicit def intToRational(n: Int): Rational = Rational(n) 45 | 46 | def sum(rationals: Rational*): Rational = 47 | if rationals.isEmpty then Zero 48 | else rationals.head + sum(rationals.tail*) 49 | 50 | extension (xs: List[Rational]) 51 | def total: Rational = 52 | if xs.isEmpty then 0 53 | else xs.head + xs.tail.total 54 | 55 | def avg: Rational = xs.total / xs.size 56 | -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/src/main/scala/scalafmi/numberextensions/ExtensionMethodsExamples.scala: -------------------------------------------------------------------------------- 1 | package scalafmi.numberextensions 2 | 3 | import mathematical.Rational 4 | 5 | @main def extensionMethodsExamples = 6 | // Extensions methods are automatically available 7 | // because they are defined in Rational's companion object 8 | val xs: List[Rational] = List(Rational(1, 2), Rational(3, 4)) 9 | xs.total 10 | xs.avg 11 | 12 | 13 | // Extension methods that become available when imported: 14 | import scalafmi.numberextensions.* 15 | 16 | 42 ** 3 17 | math.Pi.squared 18 | 19 | 20 | // Examples from the standard library: 21 | import scala.concurrent.duration.DurationInt 22 | 23 | Map(1 -> "One", 2 -> "Two") 24 | "abcdef".take(2) 25 | 5.seconds 26 | -------------------------------------------------------------------------------- /lectures/examples/03-oop-in-a-functional-language/src/main/scala/scalafmi/numberextensions/NumberExtensions.scala: -------------------------------------------------------------------------------- 1 | package scalafmi.numberextensions 2 | 3 | extension (n: Int) 4 | def squared = n * n 5 | def **(exp: Double) = math.pow(n, exp) 6 | 7 | extension (n: Double) 8 | def squared = n * n 9 | def **(exp: Double) = math.pow(n, exp) -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/build.sbt: -------------------------------------------------------------------------------- 1 | name := "key-fp-approaches" 2 | version := "0.1" 3 | 4 | scalaVersion := "3.1.1" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.scalatest" %% "scalatest" % "3.2.11" % Test 8 | ) 9 | 10 | scalacOptions ++= Seq( 11 | "-new-syntax", 12 | "-indent" 13 | ) 14 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/src/main/scala/answers/HigherOrderFunctions.scala: -------------------------------------------------------------------------------- 1 | package answers 2 | 3 | import scala.annotation.tailrec 4 | 5 | def filter[A](la: List[A], p: A => Boolean): List[A] = 6 | @tailrec 7 | def loop(rest: List[A], acc: List[A] = Nil): List[A] = 8 | if rest.isEmpty then acc 9 | else if p(rest.head) then loop(rest.tail, rest.head :: acc) 10 | else loop(rest.tail, acc) 11 | 12 | loop(la).reverse 13 | 14 | def map[A, B](la: List[A], f: A => B): List[B] = 15 | @tailrec 16 | def loop(rest: List[A], acc: List[B] = Nil): List[B] = 17 | if rest.isEmpty then acc 18 | else loop(rest.tail, f(rest.head) :: acc) 19 | 20 | loop(la).reverse 21 | 22 | def reduce[A](la: List[A], f: (A, A) => A): A = 23 | @tailrec 24 | def loop(rest: List[A], acc: A): A = 25 | if rest.isEmpty then acc 26 | else loop(rest.tail, f(rest.head, acc)) 27 | 28 | loop(la.tail, la.head) 29 | -------------------------------------------------------------------------------- /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 then 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/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 then acc 10 | else loop(rest.tail, f(rest.head) :: acc) 11 | 12 | loop(la).reverse 13 | 14 | def mapML[A, B](la: List[A])(f: A => B): List[B] = 15 | @tailrec 16 | def loop(rest: List[A], acc: List[B] = Nil): List[B] = 17 | if rest.isEmpty then acc 18 | else loop(rest.tail, f(rest.head) :: acc) 19 | 20 | loop(la).reverse 21 | 22 | mapSL(List(1, 2, 3), _ * 2) // Does not compile on Scala 2, but does on Scala 3 23 | mapSL(List(1, 2, 3), (_: Int) * 2) 24 | 25 | mapML(List(1, 2, 3))(_ * 2) // Type inference works in both Scala 2 and Scala 3 26 | mapML(List(1, 2, 3))((_: Int) * 2) 27 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/src/main/scala/exercises/HigherOrderFunctions.scala: -------------------------------------------------------------------------------- 1 | package exercises 2 | 3 | def filter[A](la: List[A], p: A => Boolean): List[A] = ??? 4 | 5 | def map[A, B](la: List[A], f: A => B): List[B] = ??? 6 | 7 | def reduce[A](la: List[A], f: (A, A) => A): A = ??? 8 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/src/main/scala/exercises/MultiParameterLists.scala: -------------------------------------------------------------------------------- 1 | package exercises 2 | 3 | @main def multiParameterListsExamples = 4 | def min[T](compare: (T, T) => Int)(a: T, b: T) = 5 | if compare(a, b) <= 0 then a 6 | else b 7 | 8 | def compareByAbsoluteValue(a: Int, b: Int) = a.abs - b.abs 9 | 10 | val minByAbsoluteValue = min(compareByAbsoluteValue) 11 | 12 | println { 13 | minByAbsoluteValue(10, -20) 14 | } 15 | 16 | println { 17 | List(-10, -30, 2, 8).reduce(minByAbsoluteValue) 18 | } 19 | 20 | extension (n: Int) 21 | def times(block: => Unit): Unit = 22 | if n == 0 then () 23 | else 24 | block 25 | (n - 1).times(block) 26 | 27 | // def times(n: Int)(block: => Unit): Unit = 28 | // if n == 0 then () 29 | // else 30 | // block 31 | // times(n - 1)(block) 32 | 33 | 4.times { 34 | println("Meow") 35 | } 36 | 37 | // extension methods can also be called this way: 38 | times(4) { 39 | println("Meow") 40 | } 41 | // They actually are functions with multiple parameters lists where the first one consists of the target object. 42 | // Be being extension methods Scala allows to call them as methods on the target object. 43 | end multiParameterListsExamples 44 | -------------------------------------------------------------------------------- /lectures/examples/04-key-fp-approaches/src/main/scala/exercises/Recursion.scala: -------------------------------------------------------------------------------- 1 | package exercises 2 | 3 | import scala.annotation.tailrec 4 | 5 | object Recursion: 6 | def fact(n: Int): Int = 7 | if n <= 1 then 1 8 | else n * fact(n - 1) 9 | 10 | def size[A](l: List[A]): Int = 11 | if l.isEmpty then 0 12 | else 1 + size(l.tail) 13 | 14 | def sum(l: List[Int]): Int = 15 | if l.isEmpty then 0 16 | else l.head + sum(l.tail) 17 | 18 | // f(5) = f(4) + f(3) 19 | def fibonacci(i: Int): Int = ??? 20 | 21 | 22 | object TailRecursion: 23 | // We could introduce inner functions if we don't want to pollute the interface 24 | // But let's use default parameters 25 | 26 | @tailrec 27 | def fact(n: Int, acc: Int = 1): Int = 28 | if n <= 1 then acc 29 | else fact(n - 1, acc * n) 30 | 31 | @tailrec 32 | def size[A](l: List[A], acc: Int = 0): Int = 33 | if l.isEmpty then acc 34 | else size(l.tail, acc + 1) 35 | 36 | @tailrec 37 | def sum(l: List[Int], acc: Int = 0): Int = 38 | if l.isEmpty then acc 39 | else sum(l.tail, l.head + acc) 40 | 41 | // @tailrec 42 | def fibonacci(i: Int): Int = ??? 43 | 44 | 45 | object MoreListFunctions: 46 | @tailrec 47 | def drop[A](la: List[A], n: Int): List[A] = 48 | if n <= 0 || la.isEmpty then la 49 | else drop(la.tail, n - 1) 50 | 51 | def nthElement[A](la: List[A], n: Int): A = ??? 52 | 53 | def reverse[A](l: List[A]): List[A] = ??? 54 | 55 | def take[A](la: List[A], n: Int): List[A] = ??? 56 | 57 | def concat(l1: List[Int], l2: List[Int]): List[Int] = ??? 58 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.5.1 2 | 3 | runner.dialect = scala3 4 | 5 | maxColumn = 120 6 | 7 | indent.defnSite = 2 8 | 9 | verticalMultiline.newlineAfterOpenParen = true 10 | verticalMultiline.arityThreshold = 5 11 | verticalMultiline.atDefnSite = true 12 | danglingParentheses.exclude = [] 13 | 14 | align.preset = none 15 | assumeStandardLibraryStripMargin = true 16 | align.stripMargin = true 17 | 18 | rewrite.scala3.convertToNewSyntax = true 19 | rewrite.scala3.removeOptionalBraces = true 20 | 21 | rewrite { 22 | rules = [ 23 | RedundantParens, 24 | SortModifiers 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/build.sbt: -------------------------------------------------------------------------------- 1 | name := "concurrency" 2 | version := "0.1" 3 | 4 | scalaVersion := "3.1.1" 5 | 6 | val akkaVersion = "2.6.19" 7 | 8 | libraryDependencies ++= Seq( 9 | ("com.typesafe.akka" %% "akka-actor" % akkaVersion).cross(CrossVersion.for3Use2_13), 10 | ("com.typesafe.akka" %% "akka-actor-typed" % akkaVersion).cross(CrossVersion.for3Use2_13), 11 | ("com.typesafe.akka" %% "akka-stream" % akkaVersion).cross(CrossVersion.for3Use2_13), 12 | ("com.typesafe.akka" %% "akka-http" % "10.2.9").cross(CrossVersion.for3Use2_13), 13 | "org.asynchttpclient" % "async-http-client" % "2.12.3", 14 | "ch.qos.logback" % "logback-classic" % "1.2.11", 15 | "org.scalatest" %% "scalatest" % "3.2.11" % Test 16 | ) 17 | 18 | scalacOptions ++= Seq( 19 | "-new-syntax", 20 | "-indent" 21 | ) 22 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") 2 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/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/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 | // We need to do complex and error prone state management 19 | // and concurrency synchronization 20 | var firstProduct: Option[Product] = None 21 | 22 | val onProduced: Product => Unit = { newProduct => 23 | this.synchronized { 24 | firstProduct match 25 | case Some(existingProduct) => onComplete(existingProduct, newProduct) 26 | case None => firstProduct = Some(newProduct) 27 | } 28 | } 29 | 30 | produceProduct(onProduced) 31 | produceProduct(onProduced) 32 | 33 | @main def run: Unit = execute { 34 | // produceProduct(println) 35 | produce2Products((p1, p2) => println((p1, p2))) 36 | } 37 | 38 | def verifyProduct(product: Product)(onVerified: Verification => Unit): Unit = execute { 39 | val verification = ProductFactory.verifyProduct(product) 40 | 41 | execute(onVerified(verification)) 42 | } 43 | 44 | def produceInPipeline(callback: (List[Product], List[Verification]) => Unit): Unit = 45 | // Callback hell 46 | produceProduct { a => 47 | verifyProduct(a) { aVerification => 48 | produceProduct { b => 49 | verifyProduct(b) { bVerification => 50 | callback(List(a, b), List(aVerification, bVerification)) 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /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 | given 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 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/concurrent/Executors.scala: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import java.util.concurrent.{Executor, ExecutorService, ForkJoinPool} 4 | 5 | object Executors: 6 | given defaultExecutor: Executor = new ForkJoinPool 7 | 8 | val currentThreadExecutor: Executor = new Executor: 9 | def execute(operation: Runnable): Unit = operation.run() 10 | 11 | val blockingExecutor: ExecutorService = java.util.concurrent.Executors.newCachedThreadPool() 12 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/concurrent/lecture/Future.scala: -------------------------------------------------------------------------------- 1 | package concurrent.lecture 2 | 3 | import concurrent.Executors 4 | import product.ProductFactory 5 | import product.ProductFactory.produceProduct 6 | import util.Utils 7 | 8 | import java.util.concurrent.{CompletableFuture, Executor, ForkJoinPool} 9 | import scala.util.Try 10 | 11 | trait Future[+A]: 12 | def value: Option[Try[A]] 13 | def isComplete: Boolean = value.nonEmpty 14 | 15 | def onComplete(callback: Try[A] => Unit)(using ex: Executor): Unit 16 | 17 | def map[B](f: A => B)(using ex: Executor): Future[B] 18 | def flatMap[B](f: A => Future[B])(using ex: Executor): Future[B] 19 | 20 | def zip[B](that: Future[B]): Future[(A, B)] 21 | def zipMap[B, R](that: Future[B])(f: (A, B) => R): Future[R] 22 | 23 | def filter(predicate: A => Boolean)(using ex: Executor): Future[A] 24 | 25 | def withFilter(f: A => Boolean)(using ex: Executor): Future[A] = filter(f) 26 | // sequence 27 | 28 | object Future: 29 | def apply[A](result: => A)(using ex: Executor): Future[A] = ??? 30 | 31 | given Executor = new ForkJoinPool() 32 | 33 | Future(ProductFactory.produceProduct("computer")) 34 | .map(_.name) 35 | 36 | def calc1 = Future { 37 | 1 + 1 38 | } 39 | 40 | def calc2 = Future { 41 | 42 42 | } 43 | 44 | def double(n: Int) = Future { 45 | n * 2 46 | } 47 | 48 | val combinedCalculation = 49 | for 50 | (r1, r2) <- calc1 zip calc2 51 | doubled <- double(r1 + r2) 52 | yield doubled -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | // we want the blocking console operations to be executing in the blocking execution context 10 | def getStringLine: IO[String] = IO(StdIn.readLine()).bindTo(blockingEc) 11 | def putStringLine(str: String): IO[Unit] = IO(println(str)).bindTo(blockingEc) 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.{ExecutionContext, Future} 12 | 13 | object FutureWebServer: 14 | given actorSystem: ActorSystem = ActorSystem() 15 | given ec: ExecutionContext = 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 | } 27 | 28 | def main(args: Array[String]): Unit = 29 | Http().newServerAt("0.0.0.0", 8080).bindFlow(routes) 30 | -------------------------------------------------------------------------------- /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 org.asynchttpclient.Dsl.* 6 | import org.asynchttpclient.* 7 | 8 | import java.util.concurrent.Executor 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 | def getIO(url: String): IO[Response] = 27 | val eventualResponse = client.prepareGet(url).setFollowRedirect(true).execute() 28 | 29 | IO.usingCallback[Response] { (ec, callback) => 30 | eventualResponse.addListener(() => callback(Try(eventualResponse.get())), r => ec.execute(r)) 31 | } 32 | 33 | def getScalaFuture(url: String): scala.concurrent.Future[Response] = 34 | val p = scala.concurrent.Promise[Response]() 35 | 36 | val eventualResponse = client.prepareGet(url).setFollowRedirect(true).execute() 37 | eventualResponse.addListener(() => p.complete(Try(eventualResponse.get())), null) 38 | 39 | p.future 40 | 41 | case class BadResponse(statusCode: Int) extends Exception 42 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/http/HttpRequests.scala: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import util.HttpServiceUrls 4 | 5 | @main def runHttpRequestsExample = 6 | import concurrent.Executors.given 7 | 8 | val myIp = HttpClient.get("http://icanhazip.com") 9 | .map(_.getResponseBody) 10 | 11 | val example = HttpClient.get("http://example.org") 12 | .map(_.getResponseBody) 13 | 14 | val endResult = for 15 | (ipResult, exampleResult) <- (myIp zip example) 16 | r <- HttpClient.get( 17 | HttpServiceUrls.randomNumberUpTo(ipResult.length + exampleResult.length)) 18 | yield r.getResponseBody 19 | 20 | endResult.foreach(println) -------------------------------------------------------------------------------- /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 | object Library: 22 | private val books = List( 23 | Book(BookId("1"), "Programming in Scala", List(AuthorId("1"), AuthorId("2")), "Computer Science"), 24 | Book(BookId("2"), "Programming Erlang", List(AuthorId("3")), "Computer Science"), 25 | Book(BookId("3"), "American Gods", List(AuthorId("4")), "Fantasy"), 26 | Book(BookId("4"), "The Fellowship of the Ring", List(AuthorId("5")), "Fantasy"), 27 | Book( 28 | BookId("5"), 29 | "The Book", 30 | List( 31 | AuthorId("1"), 32 | AuthorId("3"), 33 | AuthorId("4"), 34 | AuthorId("5") 35 | ), 36 | "Fantasy" 37 | ) 38 | ) 39 | private val authors = List( 40 | Author(AuthorId("1"), "Martin Odersky"), 41 | Author(AuthorId("2"), "Bill Venners"), 42 | Author(AuthorId("3"), "Joe Armstrong"), 43 | Author(AuthorId("4"), "Neil Gaiman"), 44 | Author(AuthorId("5"), "J. R. R. Tolkien") 45 | ) 46 | 47 | val TheGreatLibrary = new Library(books, authors) 48 | -------------------------------------------------------------------------------- /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 | def verifyProduct(product: Product): Verification = 17 | val threadId = Thread.currentThread().getId 18 | 19 | println(s"Verifying product ${product.name}...") 20 | Thread.sleep(2000) 21 | println(s"Product verified, thread: $threadId") 22 | 23 | Verification(threadId.hashCode) 24 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/referentialtransparancy/FutureReferentialTransparencyExample1.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 | @main def runFutureReferentialTransparencyExample1Variant1 = 8 | def calc[T](expr: => T) = Future { 9 | Thread.sleep(4000) 10 | 11 | expr 12 | } 13 | 14 | val futureA = calc(42) 15 | val futureB = calc(10) 16 | 17 | val sum = for 18 | a <- futureA 19 | b <- futureB 20 | yield a + b 21 | 22 | println { 23 | Await.result(sum, 5.seconds) 24 | } 25 | end runFutureReferentialTransparencyExample1Variant1 26 | 27 | // This one, unlike the one above, will timeout 28 | @main def runFutureReferentialTransparencyExample1Variant2 = 29 | def calc[T](expr: => T) = Future { 30 | Thread.sleep(4000) 31 | 32 | expr 33 | } 34 | 35 | val sum = for 36 | a <- calc(42) 37 | b <- calc(42) 38 | yield a + b 39 | 40 | println { 41 | Await.result(sum, 5.seconds) 42 | } 43 | end runFutureReferentialTransparencyExample1Variant2 44 | 45 | @main def runFutureReferentialTransparencyExample1HowToWriteItCorrectly = 46 | def calc[T](expr: => T) = Future { 47 | Thread.sleep(4000) 48 | 49 | expr 50 | } 51 | 52 | // if you want calculations executed concurrently you must use zip 53 | val sum = for 54 | (a, b) <- calc(42) zip calc(42) 55 | yield a + b 56 | 57 | println { 58 | Await.result(sum, 5.seconds) 59 | } 60 | end runFutureReferentialTransparencyExample1HowToWriteItCorrectly 61 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/referentialtransparancy/FutureReferentialTransparencyExample2.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 | @main def runFutureReferentialTransparencyExample2Variant1 = 8 | def calc[T](expr: => T) = Future { 9 | println("Hello") 10 | 11 | Thread.sleep(4000) 12 | 13 | expr 14 | } 15 | 16 | val futureCalc = calc(42) 17 | 18 | // "Hello" will be printed once 19 | val sum = for 20 | (a, b) <- futureCalc zip futureCalc 21 | yield a + b 22 | 23 | println { 24 | Await.result(sum, 5.seconds) 25 | } 26 | end runFutureReferentialTransparencyExample2Variant1 27 | 28 | @main def runFutureReferentialTransparencyExample2Variant2 = 29 | def calc[T](expr: => T) = Future { 30 | println("Hello") 31 | 32 | Thread.sleep(4000) 33 | 34 | expr 35 | } 36 | 37 | // "Hello" will be printed twice 38 | // To fix this we will introduce an alternative implementation to Future called (asynchronous) IO 39 | val sum = for 40 | (a, b) <- calc(42) zip calc(42) 41 | yield a + b 42 | 43 | println { 44 | Await.result(sum, 5.seconds) 45 | } 46 | end runFutureReferentialTransparencyExample2Variant2 47 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/threads/ThreadPoolsExample.scala: -------------------------------------------------------------------------------- 1 | package threads 2 | 3 | import java.util.concurrent.{Executor, ExecutorService, Executors, ForkJoinPool} 4 | 5 | @main def runThreadPoolsExample = 6 | val threadPool: Executor = new ForkJoinPool() 7 | 8 | threadPool.execute(() => { 9 | println("Hellooo") 10 | threadPool.execute(() => println("Additional work")) 11 | }) 12 | 13 | threadPool.execute(() => println("Running concurrently")) 14 | 15 | // In a real application we will wait for all the work to finish 16 | // and the we will stop the thread pool 17 | // Here we just sleep to simulate waiting. 18 | // Later in the course we will see haw we can achieve the above 19 | // much easier with functional programming using IO resources 20 | Thread.sleep(500) 21 | end runThreadPoolsExample 22 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/threads/ThreadsExample.scala: -------------------------------------------------------------------------------- 1 | package threads 2 | 3 | import util.Utils 4 | 5 | @main def runThreadsExample = 6 | import 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 | end runThreadsExample 50 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/threads/ThreadsSharingData.scala: -------------------------------------------------------------------------------- 1 | package threads 2 | 3 | @main def runThreadsSharingDataExample = 4 | // Ако тази променлива не беше маркирана като @volatile, 5 | // то нишката thread по-долу нямаше да вижда промените по нея 6 | @volatile var improveCalculation = true 7 | 8 | val thread = new Thread(() => 9 | var i = 0L 10 | 11 | while improveCalculation do 12 | i += 1 13 | 14 | println(s"Thread exiting: $i") 15 | ) 16 | 17 | thread.start() 18 | 19 | println("Main going to sleep...") 20 | Thread.sleep(1000) 21 | println("Main waking up...") 22 | 23 | improveCalculation = false 24 | 25 | thread.join() 26 | 27 | println("Main exiting") 28 | end runThreadsSharingDataExample 29 | -------------------------------------------------------------------------------- /lectures/examples/08-concurrency/src/main/scala/threads/ThreadsSharingData2.scala: -------------------------------------------------------------------------------- 1 | package threads 2 | 3 | @main def runThreadsSharingDataExample2 = 4 | @volatile var improveCalculation = true 5 | 6 | var result: Option[Int] = None 7 | @volatile var thread1Ready = false 8 | 9 | val thread1 = new Thread(() => { 10 | var i = 0 11 | 12 | while improveCalculation do 13 | i += 1 14 | 15 | result = Some(i) 16 | // writing to the volatile variable guarantees that 17 | // thread two will see the value of result we set above 18 | thread1Ready = true 19 | 20 | // if we change result here thread2 is not guaranteed to see it 21 | // even if it wakes up after we've made the change 22 | // result = Some(1024) 23 | 24 | println(s"Thread 1 exiting: $i") 25 | }) 26 | 27 | val thread2 = new Thread(() => { 28 | while !thread1Ready do 29 | Thread.sleep(10) 30 | 31 | println(s"Thread 2 exiting: $result") 32 | }) 33 | 34 | thread1.start() 35 | thread2.start() 36 | 37 | println("Main going to sleep...") 38 | Thread.sleep(1000) 39 | println("Main waking up...") 40 | 41 | improveCalculation = false 42 | 43 | thread1.join() 44 | thread2.join() 45 | 46 | println("Main exiting") 47 | end runThreadsSharingDataExample2 48 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | def doWork = (1 to 4000000).map(math.pow(2, _)).toList 14 | def doMoreWork = (1 to 4000000).map(math.pow(3, _)).toList 15 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes-scala-2/build.sbt: -------------------------------------------------------------------------------- 1 | name := "type-classes" 2 | version := "0.1" 3 | 4 | scalaVersion := "2.13.8" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.4", 8 | "org.scala-lang" % "scala-reflect" % "2.13.8", 9 | "org.typelevel" %% "cats-core" % "2.7.0", 10 | "org.typelevel" %% "spire" % "0.17.0", 11 | "org.scalatest" %% "scalatest" % "3.2.11" % Test 12 | ) 13 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes-scala-2/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-scala-2/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-scala-2/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-scala-2/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes-scala-2/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-scala-2/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-scala-2/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-scala-2/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-scala-2/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-scala-2/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-scala-2/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-scala-2/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-scala-2/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-scala-2/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-scala-2/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-scala-2/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-scala-2/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/09-type-classes/.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.5.1 2 | 3 | runner.dialect = scala3 4 | 5 | maxColumn = 120 6 | 7 | indent.defnSite = 2 8 | 9 | verticalMultiline.newlineAfterOpenParen = true 10 | verticalMultiline.arityThreshold = 5 11 | verticalMultiline.atDefnSite = true 12 | danglingParentheses.exclude = [] 13 | 14 | align.preset = none 15 | assumeStandardLibraryStripMargin = true 16 | align.stripMargin = true 17 | 18 | rewrite.scala3.convertToNewSyntax = true 19 | rewrite.scala3.removeOptionalBraces = true 20 | 21 | spaces.beforeContextBoundColon = Always 22 | 23 | rewrite { 24 | rules = [ 25 | RedundantParens, 26 | SortModifiers 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/build.sbt: -------------------------------------------------------------------------------- 1 | name := "type-classes" 2 | version := "0.1" 3 | 4 | scalaVersion := "3.1.1" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.4", 8 | "org.typelevel" %% "cats-core" % "2.7.0", 9 | "org.typelevel" %% "spire" % "0.18.0-M3", 10 | "org.scalatest" %% "scalatest" % "3.2.11" % Test 11 | ) 12 | -------------------------------------------------------------------------------- /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 | // Here we say that T has the Shape type class (just like we do in Scala) 29 | // which allows us to use all its operations 30 | fn print_area (shape: &T) { 31 | println!("{}", shape.area()); 32 | } 33 | 34 | fn main() { 35 | let circle = Circle{radius: 2.0}; 36 | let rectangle = Rectangle{height: 3.0, width: 5.0}; 37 | 38 | print_area(&circle); // 12.5664 39 | print_area(&rectangle); // 15 40 | } 41 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") 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 | @main def runCatsMonoidDemo = 10 | (2, 3) |+| (4, 5) 11 | 12 | given rationalMonoid: Monoid[Rational] with 13 | def empty: Rational = 0 14 | def combine(x: Rational, y: Rational): Rational = x + y 15 | 16 | val map1 = Map(1 -> (2, Rational(3, 2)), 2 -> (3, Rational(4))) 17 | val map2 = Map(2 -> (5, Rational(6)), 3 -> (7, Rational(8, 3))) 18 | 19 | println(map1 |+| map2) 20 | -------------------------------------------------------------------------------- /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 | case class Cat(name: String) 8 | case class Dog(name: String) 9 | 10 | @main def runEqDemo = 11 | // compiles 12 | Some(Rational(2)) == Rational(2) 13 | Cat("Vivian") == Dog("Rony") 14 | 15 | // won't compile, uses the Eq typeclass 16 | // Some(Rational(2)) === Rational(2) 17 | // Cat("Vivian") === Dog("Rony") 18 | // won't compile either 19 | // "0" === 2 20 | 21 | // compiles 22 | 0 === 2 23 | 24 | given Eq[Rational] with 25 | def eqv(x: Rational, y: Rational): Boolean = x == y 26 | 27 | println(Rational(5) === Rational(10, 2)) 28 | println(Rational(5, 2) =!= Rational(10, 2)) 29 | // doesn't compile 30 | // println(Rational(5, 2) === "") 31 | println(Rational(5, 2) === 2) 32 | 33 | case class Box[+A](a: A): 34 | def contains[B >: A : Eq](b: B): Boolean = b === a 35 | 36 | Box(1).contains(1) 37 | // doesn't compile 38 | // Box(1).contains("") 39 | 40 | // compiles as it doesn't use the type class 41 | List(1).contains("") 42 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/json/Person.scala: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import JsonValue.JsonObject 4 | 5 | import JsonSerializable.given 6 | 7 | case class Person(name: String, email: String, age: Int) 8 | 9 | object Person: 10 | given JsonSerializable[Person] with 11 | def toJsonValue(person: Person): JsonValue = JsonObject( 12 | Map( 13 | "name" -> person.name.toJson, 14 | "email" -> person.email.toJson, 15 | "age" -> person.age.toJson 16 | ) 17 | ) 18 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/math/ListOrderingDemo.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import scala.annotation.tailrec 4 | 5 | @main def runListOrderingDemo = 6 | given [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 then 0 13 | else if x.isEmpty then -1 14 | else if y.isEmpty then 1 15 | else if x.head < y.head then -1 16 | else if y.head < x.head then 1 17 | else loop(x.tail, y.tail) 18 | 19 | loop(x, y) 20 | 21 | val sortedList = List( 22 | List(1, 2, 3), 23 | List(1, 3, 2), 24 | List(3, 4), 25 | List.empty[Int], 26 | List(1, 1) 27 | ).sorted 28 | 29 | println(sortedList) 30 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/math/Monoid.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | trait Monoid[M] extends Semigroup[M]: 4 | extension (a: M) 5 | def |+|(b: M): M // no need to repeat this one from Semigroup, it's here just for completeness 6 | 7 | def identity: M 8 | 9 | object Monoid: 10 | def apply[A](using m: Monoid[A]): Monoid[A] = m 11 | 12 | given Monoid[Int] with 13 | extension (a: Int) def |+|(b: Int): Int = a + b 14 | val identity: Int = 0 15 | 16 | val intMultiplicativeMonoid: Monoid[Int] = new Monoid[Int]: 17 | extension (a: Int) def |+|(b: Int): Int = a * b 18 | val identity: Int = 1 19 | 20 | given Monoid[String] with 21 | extension (a: String) def |+|(b: String): String = a + b 22 | val identity: String = "" 23 | 24 | given [A : Monoid]: Monoid[Option[A]] with 25 | extension (ma: Option[A]) def |+|(mb: Option[A]): Option[A] = (ma, mb) match 26 | case (Some(a), Some(b)) => Some(a |+| b) 27 | case (Some(_), _) => ma 28 | case (_, Some(_)) => mb 29 | case _ => None 30 | 31 | def identity: Option[A] = None 32 | 33 | given [A : Monoid, B : Monoid]: Monoid[(A, B)] with 34 | extension (p1: (A, B)) def |+|(p2: (A, B)): (A, B) = (p1, p2) match 35 | case ((a1, b1), (a2, b2)) => (a1 |+| a2, b1 |+| b2) 36 | 37 | def identity: (A, B) = (Monoid[A].identity, Monoid[B].identity) 38 | 39 | given [K, V : Monoid]: Monoid[Map[K, V]] with 40 | extension (a: Map[K, V]) def |+|(b: Map[K, V]): Map[K, V] = 41 | val vIdentity = Monoid[V].identity 42 | 43 | (a.keySet ++ b.keySet).foldLeft(identity) { (acc, key) => 44 | acc + (key -> (a.getOrElse(key, vIdentity) |+| b.getOrElse(key, vIdentity))) 45 | } 46 | 47 | def identity: Map[K, V] = Map.empty[K, V] 48 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/math/MonoidDemo.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | @main def runMonoidDemo = 4 | def sum[A: Monoid](xs: List[A]) = 5 | xs.foldLeft(Monoid[A].identity)(_ |+| _) 6 | 7 | // ad-hoc polymorphism 8 | sum(List(1, 3, 4)) // 8 9 | sum(List("a", "b", "c")) // "abc" 10 | sum(List(Rational(1, 2), Rational(3, 4))) // 5/4 11 | 12 | sum(List.empty[Rational]) // 0/1 13 | 14 | sum(List(Rational(3, 4), Rational(5), Rational(7, 4), Rational(11, 13))) // 217/26 15 | sum(List(Rational(1, 2), Rational(4))) // 6/8 16 | sum(List(1, 2, 3, 4, 5)) // 15 17 | 18 | // We can explicitly use a different Monoid for a single call 19 | sum(List(1, 2, 3, 4, 5))(using Monoid.intMultiplicativeMonoid) // 15 20 | 21 | { 22 | // An we can also explicitly change the context (with a new implicit val or with an import of an implicit val) 23 | 24 | given Monoid[Rational] = Rational.rationalMultiplicativeMonoid 25 | 26 | given Monoid[Int] = Monoid.intMultiplicativeMonoid 27 | 28 | sum(List(Rational(3, 4), Rational(5), Rational(7, 4), Rational(11, 13))) // 1155/208 29 | sum(List(Rational(1, 2), Rational(4))) // 2/1 30 | sum(List(1, 2, 3, 4, 5)) // 120 31 | } 32 | 33 | // Composite monoids: 34 | 35 | // Option: 36 | 37 | sum( 38 | List( 39 | Some(Rational(1)), 40 | None, 41 | Some(Rational(1, 2)), 42 | Some(Rational(3, 8)), 43 | None 44 | ) 45 | ) // Some(15/8) 46 | 47 | import Monoid.given 48 | 49 | // Pairs 50 | (2, 3) |+| (4, 5) // (6, 8) 51 | 52 | val map1 = Map(1 -> (2, Rational(3, 2)), 2 -> (3, Rational(4))) 53 | val map2 = Map(2 -> (5, Rational(6)), 3 -> (7, Rational(8, 3))) 54 | 55 | // Composes Monoid[Int] and Monoid[Rational] into a pair monoid Monoid[(Int, Rational)] 56 | // and then composes the pair monoid into Monoid[Map[Int, (Int, Rational)] 57 | println(map1 |+| map2) -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/math/NumericTypeclassDemo.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | @main def NumericTypeclassDemo = 4 | // Both use Numeric type class 5 | List(1, 2, 3, 4).sum 6 | List(1, 2, 3, 4).product 7 | 8 | // does not compile: could not find a given for parameter num: Numeric[String] 9 | // List("a", "b", "cd").sum 10 | 11 | given Numeric[Rational] with 12 | def plus(x: Rational, y: Rational): Rational = x + y 13 | def minus(x: Rational, y: Rational): Rational = x - y 14 | def times(x: Rational, y: Rational): Rational = x * y 15 | def negate(x: Rational): Rational = -x 16 | 17 | def fromInt(x: Int): Rational = x 18 | def toInt(x: Rational): Int = x.numer / x.denom 19 | def toLong(x: Rational): Long = x.numer / x.denom 20 | def toFloat(x: Rational): Float = x.numer.toFloat / x.denom 21 | def toDouble(x: Rational): Double = x.numer.toDouble / x.denom 22 | 23 | def compare(x: Rational, y: Rational): Int = x compare y 24 | 25 | def parseString(str: String): Option[Rational] = str.split("/") match 26 | case Array(nStr, dStr) => 27 | for 28 | n <- Numeric[Int].parseString(nStr) 29 | d <- Numeric[Int].parseString(dStr) 30 | yield Rational(n, d) 31 | case _ => None 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 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/math/OrderingTypeClassDemo.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import scala.math.abs 4 | 5 | @main def runOrderingTypeClassDemo = 6 | def quickSort[T](xs: List[T])(using 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 | given Ordering[Int] = Ordering.Int.reverse 16 | 17 | quickSort(List(5, 1, 2, 3)) // List(5, 3, 2, 1) 18 | quickSort(List(-5, 1, 2, -2, 3)) // List(3, 2, 1, -2, -5) 19 | quickSort(List(-5, 1, 2, -2, 3))(using Ordering.by(abs)) // List(-5, 3, 2, -2, 1) 20 | -------------------------------------------------------------------------------- /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 | @tailrec 15 | private def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a % b) 16 | 17 | def unary_- = new Rational(-numer, denom) 18 | def inverse = new Rational(denom, numer) 19 | 20 | def +(that: Rational) = new Rational( 21 | numer * that.denom + that.numer * denom, 22 | denom * that.denom 23 | ) 24 | def -(that: Rational) = this + (-that) 25 | def *(that: Rational) = new Rational( 26 | numer * that.numer, 27 | denom * that.denom 28 | ) 29 | def /(that: Rational) = this * that.inverse 30 | 31 | override def equals(other: Any): Boolean = other match 32 | case that: Rational => numer == that.numer && denom == that.denom 33 | case _ => false 34 | override def hashCode(): Int = (numer, denom).## 35 | 36 | override def toString: String = s"$numer/$denom" 37 | 38 | def compare(that: Rational): Int = (this - that).numer 39 | 40 | object Rational: 41 | def identity: Rational = Rational(0) 42 | 43 | def apply(n: Int, d: Int = 1) = new Rational(n, d) 44 | 45 | implicit def intToRational(n: Int): Rational = new Rational(n) 46 | 47 | given Monoid[Rational] with 48 | extension (a: Rational) def |+|(b: Rational): Rational = a + b 49 | def identity: Rational = 0 50 | 51 | val rationalMultiplicativeMonoid: Monoid[Rational] = new Monoid[Rational]: 52 | extension (a: Rational) def |+|(b: Rational): Rational = a * b 53 | def identity: Rational = 1 54 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/math/Semigroup.scala: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | trait Semigroup[M]: 4 | extension (a: M) 5 | def |+|(b: M): M 6 | 7 | object Semigroup: 8 | def apply[A](using m: Semigroup[A]): Semigroup[A] = m 9 | -------------------------------------------------------------------------------- /lectures/examples/09-type-classes/src/main/scala/parallel/ParallelCollections.scala: -------------------------------------------------------------------------------- 1 | package parallel 2 | 3 | import math.Monoid 4 | import parallel.Utils.time 5 | 6 | import scala.collection.parallel.CollectionConverters.* 7 | import scala.collection.parallel.ParSeq 8 | 9 | def sum[A : Monoid](xs: Seq[A]): A = 10 | xs.fold(Monoid[A].identity)(_ |+| _) 11 | 12 | def sum[A : Monoid](xs: ParSeq[A]): A = 13 | xs.fold(Monoid[A].identity)(_ |+| _) 14 | 15 | @main def runParallelCollectionsDemo = 16 | val seq = 1 to 900000000 17 | 18 | // Non-associative operation 19 | given badMonoid: Monoid[Int] with 20 | extension (a: Int) def |+|(b: Int): Int = a - b 21 | def identity: Int = 0 22 | 23 | // when using the badMonoid the two will produce different results 24 | println(time("Single threaded")(sum(seq))) 25 | println(time("Multi threaded")(sum(seq.par))) 26 | 27 | object Utils: 28 | def time[T](name: String)(operation: => T): T = 29 | val startTime = System.currentTimeMillis() 30 | 31 | val result = operation 32 | 33 | println(s"$name took ${System.currentTimeMillis - startTime} millis") 34 | 35 | result 36 | -------------------------------------------------------------------------------- /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 | @main def runVectorSpaceDemo = 8 | val vectorSpaceExpr = 5 *: Vector(1, 2, 3) + -1 *: Vector(3, 4, -5) 9 | 10 | println(vectorSpaceExpr) 11 | 12 | given [A]: VectorSpace[A => Double, Double] with 13 | given 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 | val doubling = (n: Double) => n * 2 21 | val square = (n: Double) => n * n 22 | 23 | val composed = 4 *: doubling - 3.14 *: square 24 | 25 | println { 26 | composed(10) 27 | } 28 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.5.1 2 | 3 | runner.dialect = scala3 4 | 5 | maxColumn = 120 6 | 7 | indent.defnSite = 2 8 | 9 | verticalMultiline.newlineAfterOpenParen = true 10 | verticalMultiline.arityThreshold = 5 11 | verticalMultiline.atDefnSite = true 12 | danglingParentheses.exclude = [] 13 | 14 | align.preset = none 15 | assumeStandardLibraryStripMargin = true 16 | align.stripMargin = true 17 | 18 | rewrite.scala3.convertToNewSyntax = true 19 | rewrite.scala3.removeOptionalBraces = true 20 | 21 | spaces.beforeContextBoundColon = Always 22 | 23 | rewrite { 24 | rules = [ 25 | RedundantParens, 26 | SortModifiers 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/build.sbt: -------------------------------------------------------------------------------- 1 | name := "monads-and-applicatives" 2 | version := "0.1" 3 | 4 | scalaVersion := "3.1.1" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.typelevel" %% "cats-core" % "2.7.0" 8 | ) 9 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") 2 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/effects/Functor.scala: -------------------------------------------------------------------------------- 1 | package effects 2 | 3 | trait Functor[F[_]]: 4 | extension [A](fa: F[A]) 5 | def map[B](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 | object Functor: 25 | def apply[F[_]](using f: Functor[F]): Functor[F] = f 26 | 27 | // Example implementation for Option 28 | given Functor[Option] with 29 | extension [A](fa: Option[A]) 30 | def map[B](f: A => B): Option[B] = fa match 31 | case None => None 32 | case Some(a) => Some(f(a)) 33 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/effects/id/Id.scala: -------------------------------------------------------------------------------- 1 | package effects.id 2 | 3 | import effects.Monad 4 | 5 | type Id[A] = A 6 | 7 | given Monad[Id] with 8 | def unit[A](a: A): Id[A] = a 9 | 10 | extension [A](fa: Id[A]) 11 | def flatMap[B](f: A => Id[B]): Id[B] = f(fa) 12 | 13 | @main def runIdDemo = 14 | val r = for 15 | a <- "Hello, " 16 | b <- "World!" 17 | yield a + b 18 | 19 | val a = "Hello, " 20 | val b = "World!" 21 | val r2 = a + b 22 | 23 | // These two lines are equivalent in meaning 24 | a + b 25 | Monad[Id].map2(a, b)(_ + _) 26 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/effects/maybe/Maybe.scala: -------------------------------------------------------------------------------- 1 | package effects.maybe 2 | 3 | import effects.Monad 4 | 5 | sealed trait Maybe[+A] 6 | case class Just[+A](a: A) extends Maybe[A] 7 | case object Nthng extends Maybe[Nothing] 8 | 9 | object Maybe: 10 | given Monad[Maybe] with 11 | def unit[A](a: A): Maybe[A] = Just(a) 12 | 13 | extension [A](fa: Maybe[A]) 14 | def flatMap[B](f: A => Maybe[B]): Maybe[B] = fa match 15 | case Just(a) => f(a) 16 | case _ => Nthng 17 | 18 | @main def runMaybeDemo = 19 | def f(n: Int): Maybe[Int] = Just(n + 1) 20 | def g(n: Int): Maybe[Int] = Just(n * 2) 21 | def h(n: Int): Maybe[Int] = Just(n * n) 22 | 23 | val a = 3 24 | 25 | val result = for 26 | b <- f(a) 27 | c <- g(b * 2) 28 | d <- h(b + c) 29 | yield a * b * d 30 | println(result) 31 | 32 | val maybe1: Maybe[Int] = Just(42) 33 | val maybe2: Maybe[Int] = Just(10) 34 | 35 | Monad[Maybe].map2(maybe1, maybe2)(_ + _) 36 | 37 | val listOfMaybes = List(Just(1), Nthng, Just(2)) 38 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/effects/state/RNG.scala: -------------------------------------------------------------------------------- 1 | package effects.state 2 | 3 | import effects.Monad 4 | import effects.state.State.* 5 | 6 | case class RNG(seed: Long): 7 | def nextInt: (RNG, Int) = 8 | val newSeed = (seed * 0x5deece66dL + 0xbL) & 0xffffffffffffL 9 | val nextRNG = RNG(newSeed) 10 | val n = (newSeed >>> 16).toInt 11 | 12 | (nextRNG, n) 13 | 14 | object RNG: 15 | val nextInt = State((rng: RNG) => rng.nextInt) 16 | val nextBoolean = nextInt.map(_ > 0) 17 | 18 | @main def runRNGDemo = 19 | import RNG.* 20 | 21 | // Without State we have to do this: 22 | val (rng1, next1) = RNG(System.currentTimeMillis).nextInt 23 | val (rng2, next2) = rng1.nextInt 24 | val (rng3, next3) = rng2.nextInt 25 | 26 | println((next1, next2, next3)) 27 | 28 | // With state 29 | val randomTuple = for 30 | a <- nextInt 31 | b <- nextInt 32 | c <- nextBoolean 33 | d <- nextBoolean 34 | yield (a, b, a + b, c) 35 | 36 | println(randomTuple.run(RNG(System.currentTimeMillis))) 37 | 38 | val b = nextInt.flatMap(i => nextBoolean) 39 | -------------------------------------------------------------------------------- /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 | // [A] =>> State[S, A] is a type lambda – a type-level function which 9 | // accepts a type parameter and produces a specific type – in this case 10 | // the function accepts the parameter A and produces the type State[S, A]. 11 | // 12 | // In meaning this is equivalent to having: 13 | // 14 | // type S = ??? 15 | // type StateMonad[A] = State[S, A] 16 | // 17 | // and to passing StateMonad to `Monad` as type parameter. StateMonad is a type-level function too, 18 | // but unlike the lambda above it has a name 19 | // 20 | // Both of these lines are equivalent in Scala 3: 21 | // type StateMonad[A] = State[S, A] 22 | // type StateMonad = [A] =>> State[S, A] 23 | 24 | given [S]: Monad[[A] =>> State[S, A]] with 25 | extension [A](fa: State[S, A]) 26 | def flatMap[B](f: A => State[S, B]): State[S, B] = State { s1 => 27 | val (s2, a) = fa.run(s1) 28 | f(a).run(s2) 29 | } 30 | 31 | def unit[A](a: A): State[S, A] = State(s => (s, a)) 32 | -------------------------------------------------------------------------------- /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 | case object UsernameHasSpecialCharacters extends DomainValidation: 7 | def errorMessage: String = "Username cannot contain special characters." 8 | 9 | case object PasswordDoesNotMeetCriteria extends DomainValidation: 10 | def errorMessage: String = 11 | "Password must be at least 10 characters long, including an uppercase and a lowercase letter, one number and one special character." 12 | -------------------------------------------------------------------------------- /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 | object FormValidator extends FormValidator 25 | 26 | @main def runFormValidationDemo = 27 | println { 28 | FormValidator.validateForm( 29 | username = "fake#$rname", 30 | password = "password" 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/validation/FormValidatorNec.scala: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import effects.{Monad as 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]+$") then 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]).*$") then password.validNec 17 | else PasswordDoesNotMeetCriteria.invalidNec 18 | 19 | given MyMonad[ValidationResult] with 20 | def unit[A](a: A): ValidationResult[A] = Valid(a) 21 | 22 | extension [A](fa: ValidationResult[A]) 23 | def flatMap[B](f: A => ValidationResult[B]): ValidationResult[B] = fa match 24 | case Valid(a) => f(a) 25 | case i @ Invalid(_) => i 26 | 27 | def validateForm(username: String, password: String): ValidationResult[RegistrationData] = 28 | MyMonad[ValidationResult].map2( 29 | validateUserName(username), 30 | validatePassword(password) 31 | )(RegistrationData.apply) 32 | 33 | @main def runFormValidatorNecDemo = 34 | println { 35 | FormValidatorNec.validateForm( 36 | username = "fake$Us#rname", 37 | password = "password" 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /lectures/examples/10-monads-and-applicatives/src/main/scala/validation/RegistrationData.scala: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | case class RegistrationData(username: String, password: String) 4 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.5.1 2 | 3 | runner.dialect = scala3 4 | 5 | maxColumn = 120 6 | 7 | indent.defnSite = 2 8 | 9 | verticalMultiline.newlineAfterOpenParen = true 10 | verticalMultiline.arityThreshold = 5 11 | verticalMultiline.atDefnSite = true 12 | danglingParentheses.exclude = [] 13 | 14 | align.preset = none 15 | assumeStandardLibraryStripMargin = true 16 | align.stripMargin = true 17 | 18 | rewrite.scala3.convertToNewSyntax = true 19 | rewrite.scala3.removeOptionalBraces = true 20 | 21 | spaces.beforeContextBoundColon = Always 22 | 23 | rewrite { 24 | rules = [ 25 | RedundantParens, 26 | SortModifiers 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/build.sbt: -------------------------------------------------------------------------------- 1 | name := "cats-and-cats-effects" 2 | version := "0.1" 3 | 4 | scalaVersion := "3.1.1" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.typelevel" %% "cats-core" % "2.6.1", 8 | "org.typelevel" %% "cats-effect" % "3.3.11", 9 | "org.scalatest" %% "scalatest" % "3.2.11" % Test, 10 | "org.typelevel" %% "cats-laws" % "2.6.1" % Test, 11 | "org.typelevel" %% "discipline-scalatest" % "2.1.5" % Test, 12 | "org.typelevel" %% "cats-effect-testing-scalatest" % "1.4.0" % Test 13 | ) 14 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") 2 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/Ex10ConcurrentParallelDemo.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 | @main def runConcurrentParallelDemo = 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 | object Utils: 49 | def time[T](name: String)(operation: => T): T = 50 | val startTime = System.currentTimeMillis() 51 | 52 | val result = operation 53 | 54 | println(s"$name took ${System.currentTimeMillis - startTime} millis") 55 | 56 | result 57 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/Ex1DataTypesSyntax.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | //import cats.implicits.* 4 | import cats.syntax.either.* 5 | import cats.syntax.option.* 6 | import cats.syntax.validated.* 7 | 8 | @main def runDataTypesSyntax = 9 | // Option 10 | val maybeOne = 1.some 11 | val maybeN = none[Int] 12 | 13 | val either = maybeOne.toRightNec("It's not there :(") 14 | val validated = maybeOne.toValidNec("It's not there :(") 15 | 16 | val integer = maybeN.orEmpty 17 | 18 | // Validated 19 | 20 | val validatedOne = 1.validNec 21 | val validatedN = "Error".invalidNec 22 | 23 | validatedOne.toEither 24 | 25 | // Either 26 | 27 | val eitherOne = 1.asRight 28 | val eitherN = "Error".asLeft 29 | 30 | val eitherOneChain = 1.rightNec 31 | val eitherNChain = "Error".leftNec 32 | 33 | val recoveredEither = eitherN.recover { case "Error" => 34 | 42.asRight 35 | } 36 | 37 | eitherOneChain.toValidated 38 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/Ex2EqDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import math.Rational 4 | import cats.instances.all.* 5 | import cats.syntax.eq.* 6 | 7 | case class Cat(name: String) 8 | case class Dog(name: String) 9 | 10 | @main def runEqDemo = 11 | // compiles 12 | Some(Rational(2)) == Rational(2) 13 | Cat("Vivian") == Dog("Rony") 14 | 15 | // won't compile, uses the Eq typeclass 16 | // Some(Rational(2)) === Rational(2) 17 | // Cat("Vivian") === Dog("Rony") 18 | // won't compile either 19 | // "0" === 2 20 | 21 | // compiles 22 | 0 === 2 23 | 24 | given Eq[Rational] with 25 | def eqv(x: Rational, y: Rational): Boolean = x == y 26 | 27 | println(Rational(5) === Rational(10, 2)) 28 | println(Rational(5, 2) =!= Rational(10, 2)) 29 | // doesn't compile 30 | // println(Rational(5, 2) === "") 31 | println(Rational(5, 2) === 2) 32 | 33 | case class Box[+A](a: A): 34 | def contains[B >: A : Eq](b: B): Boolean = b === a 35 | 36 | Box(1).contains(1) 37 | // doesn't compile 38 | // Box(1).contains("") 39 | 40 | // compiles as it doesn't use the type class 41 | List(1).contains("") 42 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/Ex3MonoidDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import cats.syntax.monoid.* 4 | import cats.instances.all.* 5 | 6 | @main def runMonoidDemo = 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 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/Ex4FoldableDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import cats.syntax.foldable.* 4 | 5 | @main def runFoldableDemo = 6 | def doSomething[F[_] : Foldable, A : Monoid](f: F[A]): A = 7 | println { 8 | f.forall(_ != Monoid[A].empty) 9 | } 10 | 11 | println { 12 | f.foldMap(_.toString) 13 | } 14 | 15 | f.combineAll 16 | 17 | 18 | println { 19 | doSomething(List(10, 20, 30, 42)) 20 | } 21 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/Ex5FunctorDemo.scala: -------------------------------------------------------------------------------- 1 | package cats 2 | 3 | import cats.instances.all.* 4 | import cats.syntax.functor.* 5 | 6 | @main def runFunctorDemo = 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 | println { 18 | Option(10).as(42) 19 | } 20 | 21 | // def doSomething: Future[Unit] = ??? 22 | println { 23 | Option(10).void 24 | } 25 | 26 | trait Fruit 27 | case class Apple(color: String) extends Fruit 28 | case class Orange(sort: String) extends Fruit 29 | 30 | val setFruit: Option[Fruit] = Option(Apple("red")).widen -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/Ex7FlatMapMonadMonadErrorDemo.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 | @main def runFlatMapMonadMonadErrorDemo = 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 | val user = User("A", Email("a", "gmail.com"), "123") 29 | // registerUser(user) 30 | 31 | // TODO: >> and >>= examples 32 | 33 | // Monad 34 | 35 | def asyncIncrement(n: Int) = Future { 36 | println(s"Contacting our incremental service to increment $n...") 37 | 38 | n + 1 39 | } 40 | 41 | val asyncCalculation = 1.iterateWhileM(asyncIncrement)(_ < 10) 42 | println(Await.result(asyncCalculation, 1.second)) 43 | 44 | // ApplicativeError & MonadError 45 | // They add things like recoverWith, orElse, ... 46 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/cats/Ex9ParallelDemo.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 | @main def runParallelDemo = 14 | FunctionK 15 | 16 | def registerUser(token: String)(name: String, email: String): EitherNec[String, User] = for 17 | token <- verifyUserToken(token) 18 | user <- ( 19 | validateName(name), 20 | validateEmail(email) 21 | ).parMapN(User.apply) 22 | yield user 23 | 24 | println(registerUser("ssff")("", "boyanboyan.com")) 25 | 26 | println { 27 | (List(1, 2, 3), List(10, 20, 30), List(100, 200, 300)).mapN((x, y, z) => x + y + z) 28 | } 29 | println { 30 | (List(1, 2, 3), List(10, 20, 30), List(100, 200, 300)).parMapN((x, y, z) => x + y + z) 31 | } 32 | 33 | object AlternativeUserRegistration: 34 | def verifyUserToken(token: String): EitherNec[String, String] = 35 | if token.length > 10 then token.rightNec 36 | else s"Invalid token: $token".leftNec 37 | 38 | def validateName(name: String): EitherNec[String, String] = 39 | if name.nonEmpty then name.rightNec 40 | else "Name is empty".leftNec 41 | 42 | def validateEmail(email: String): EitherNec[String, Email] = email match 43 | case Email(user, domain) => Email(user, domain).rightNec 44 | case _ => s"Email is invalid: $email".leftNec 45 | 46 | case class User(name: String, email: Email) 47 | -------------------------------------------------------------------------------- /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 | import cats.implicits.* 7 | 8 | import scala.concurrent.duration.DurationInt 9 | 10 | object Ex01RunningIO: 11 | def double(n: Int): IO[Int] = IO.sleep(1.second) >> IO(n * 2) 12 | 13 | def square(n: Int): IO[Int] = IO.sleep(1.second) >> IO(n * n) 14 | 15 | val calc = for 16 | combined <- (square(2), square(10), square(20)).parTupled 17 | (a, b, c) = combined 18 | d <- double(a + b + c) 19 | yield d 20 | 21 | val calcOutput = calc.timed >>= IO.println 22 | 23 | def main(args: Array[String]): Unit = 24 | calcOutput.unsafeRunSync() 25 | 26 | // calc.unsafeRunAsync(result => println(result)) 27 | // calc.unsafeToFuture().foreach(println)(global.compute) 28 | -------------------------------------------------------------------------------- /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 | object Ex02IOAppSimple extends IOApp.Simple: 10 | def run: IO[Unit] = Ex01RunningIO.calcOutput 11 | -------------------------------------------------------------------------------- /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 | object Ex03FibersBetter extends IOApp.Simple: 22 | def run: IO[Unit] = 23 | val result = IO.both( 24 | Ex01RunningIO.double(10), 25 | Ex01RunningIO.square(10) 26 | ) 27 | .map(tupled(_ + _)) 28 | 29 | result.timed.flatMap(IO.println) 30 | 31 | object Ex03FibersBest extends IOApp.Simple: 32 | def run: IO[Unit] = 33 | val result = ( 34 | Ex01RunningIO.double(10), 35 | Ex01RunningIO.square(10) 36 | ).parMapN(_ + _) 37 | 38 | result.timed >>= IO.println 39 | -------------------------------------------------------------------------------- /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 | object Ex04Cancellation2 extends IOApp.Simple: 17 | def run: IO[Unit] = 18 | val calc1 = IO.sleep(2.second) >> IO.println("Running calc 1") >> IO.pure(42) 19 | val calc2 = IO.sleep(1.second) >> IO.println("Running calc 2") >> IO.raiseError[Int](new RuntimeException("Error")) 20 | 21 | // (calc1, calc2).parMapN(_ + _).timed >>= IO.println 22 | // ( 23 | // calc1.onCancel(IO.println("calc 1 cancelled")), 24 | // calc2.onCancel(IO.println("calc 2 cancelled")) 25 | // ).parMapN(_ + _) >>= IO.println 26 | IO.race(calc1, calc2.handleErrorWith(_ => IO.never)) >>= IO.println 27 | // IO.race(calc1, IO.sleep(1.seconds)) >>= IO.println // run something and cancel it if it does not finish in time 28 | -------------------------------------------------------------------------------- /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 | import cats.implicits.* 6 | 7 | object Ex05Resource extends IOApp.Simple: 8 | import cats.effect.{IO, Resource} 9 | 10 | import java.io.* 11 | 12 | def inputStream(f: File): Resource[IO, FileInputStream] = 13 | Resource.make { 14 | IO.blocking(new FileInputStream(f)) 15 | } { inStream => 16 | IO.blocking(inStream.close()).handleErrorWith(_ => IO.unit) 17 | } 18 | 19 | def outputStream(f: File): Resource[IO, FileOutputStream] = Resource.fromAutoCloseable(IO(new FileOutputStream(f))) 20 | 21 | def inputOutputStreams(in: File, out: File): Resource[IO, (InputStream, OutputStream)] = 22 | (inputStream(in), outputStream(out)).tupled 23 | 24 | def transmit(origin: InputStream, destination: OutputStream, buffer: Array[Byte], acc: Long): IO[Long] = for 25 | amount <- IO.blocking(origin.read(buffer, 0, buffer.length)) 26 | count <- 27 | if amount > -1 then 28 | IO.blocking(destination.write(buffer, 0, amount)) >> 29 | transmit(origin, destination, buffer, acc + amount) 30 | else IO.pure(acc) 31 | yield count 32 | 33 | def transfer(origin: InputStream, destination: OutputStream): IO[Long] = 34 | transmit(origin, destination, new Array[Byte](1024 * 10), 0L) 35 | 36 | def copy(origin: File, destination: File): IO[Long] = 37 | inputOutputStreams(origin, destination).use { case (in, out) => 38 | transfer(in, out) 39 | } 40 | 41 | def run: IO[Unit] = 42 | copy(new File("build.sbt"), new File("build-copy.sbt")) >>= 43 | (c => IO.println(s"Transfered $c bytes")) 44 | -------------------------------------------------------------------------------- /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 | object Channel: 20 | def apply[A](): IO[Channel[A]] = for 21 | ref <- IO.ref(0) 22 | q <- Queue.unbounded[IO, A] 23 | yield new Channel(ref, q) 24 | 25 | object Ex06SharedConcurrentAccess { 26 | // See tests in ChannelTestSuite 27 | } 28 | -------------------------------------------------------------------------------- /lectures/examples/11-cats-and-cats-effects/src/main/scala/io/Ex7Refs.scala: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import cats.effect.{IO, IOApp, Ref, Sync} 4 | import cats.syntax.all.* 5 | 6 | class Worker[F[_]](number: Int, ref: Ref[F, Int])(implicit F: Sync[F]): 7 | private def putStrLn(value: String): F[Unit] = F.delay(println(value)) 8 | 9 | def start: F[Unit] = 10 | for 11 | c1 <- ref.get 12 | _ <- putStrLn(show"#$number >> $c1") 13 | c2 <- ref.modify(x => (x + 1, x)) 14 | _ <- putStrLn(show"#$number >> $c2") 15 | yield () 16 | 17 | val program: IO[Int] = 18 | for 19 | ref <- Ref[IO].of(0) 20 | w1 = new Worker[IO](1, ref) 21 | w2 = new Worker[IO](2, ref) 22 | w3 = new Worker[IO](3, ref) 23 | _ <- List( 24 | w1.start, 25 | w2.start, 26 | w3.start 27 | ).parSequence.void 28 | refFinal <- ref.get 29 | yield refFinal 30 | 31 | object Ex07Refs extends IOApp.Simple: 32 | def run: IO[Unit] = program >>= IO.println 33 | -------------------------------------------------------------------------------- /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 | @tailrec 17 | private def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a % b) 18 | 19 | def unary_- = new Rational(-numer, denom) 20 | def inverse = new Rational(denom, numer) 21 | 22 | def +(that: Rational) = new Rational( 23 | numer * that.denom + that.numer * denom, 24 | denom * that.denom 25 | ) 26 | def -(that: Rational) = this + (-that) 27 | def *(that: Rational) = new Rational( 28 | numer * that.numer, 29 | denom * that.denom 30 | ) 31 | def /(that: Rational) = this * that.inverse 32 | 33 | override def equals(other: Any): Boolean = other match 34 | case that: Rational => numer == that.numer && denom == that.denom 35 | case _ => false 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 | object Rational: 43 | def apply(n: Int, d: Int = 1) = new Rational(n, d) 44 | 45 | implicit def intToRational(n: Int): Rational = new Rational(n) 46 | 47 | // val rationalEq = new Eq[Rational]: 48 | // def eqv(x: Rational, y: Rational): Boolean = x == y 49 | 50 | given Eq[Rational] = Eq.fromUniversalEquals 51 | 52 | given Monoid[Rational] = new Monoid[Rational]: 53 | def empty: Rational = 0 54 | def combine(x: Rational, y: Rational): Rational = x - y 55 | -------------------------------------------------------------------------------- /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 | object UserRegistration: 31 | def validateName(name: String): ValidatedNec[RegistrationFormError, String] = 32 | if name.nonEmpty then name.validNec 33 | else NameIsEmpty.invalidNec 34 | 35 | def validateEmail(email: String): ValidatedNec[RegistrationFormError, Email] = email match 36 | case Email(user, domain) => Email(user, domain).validNec 37 | case _ => InvalidEmail(email).invalidNec 38 | 39 | def validatePassword(password: String): ValidatedNec[RegistrationFormError, String] = 40 | if password.length > 8 then password.validNec.map(p => s"hashed $p") 41 | else PasswordTooShort.invalidNec 42 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.5.1 2 | 3 | runner.dialect = scala3 4 | 5 | maxColumn = 120 6 | 7 | indent.defnSite = 2 8 | 9 | verticalMultiline.newlineAfterOpenParen = true 10 | verticalMultiline.arityThreshold = 5 11 | verticalMultiline.atDefnSite = true 12 | danglingParentheses.exclude = [] 13 | 14 | align.preset = none 15 | assumeStandardLibraryStripMargin = true 16 | align.stripMargin = true 17 | 18 | rewrite.scala3.convertToNewSyntax = true 19 | rewrite.scala3.removeOptionalBraces = true 20 | 21 | spaces.beforeContextBoundColon = Always 22 | 23 | rewrite { 24 | rules = [ 25 | RedundantParens, 26 | SortModifiers 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/build.sbt: -------------------------------------------------------------------------------- 1 | name := "building-a-scala-app" 2 | version := "0.1" 3 | 4 | scalaVersion := "3.1.1" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.typelevel" %% "cats-core" % "2.7.0", 8 | "org.typelevel" %% "cats-effect" % "3.3.12", 9 | "org.tpolecat" %% "doobie-core" % "1.0.0-RC2", 10 | "org.tpolecat" %% "doobie-hikari" % "1.0.0-RC2", 11 | "org.tpolecat" %% "doobie-postgres" % "1.0.0-RC2", 12 | "com.typesafe" % "config" % "1.4.2", 13 | "co.fs2" %% "fs2-core" % "3.2.7", 14 | "co.fs2" %% "fs2-io" % "3.2.7", 15 | "co.fs2" %% "fs2-reactive-streams" % "3.2.7", 16 | "io.circe" %% "circe-core" % "0.14.1", 17 | "io.circe" %% "circe-generic" % "0.14.1", 18 | "io.circe" %% "circe-parser" % "0.14.1", 19 | "org.http4s" %% "http4s-dsl" % "0.23.11", 20 | "org.http4s" %% "http4s-blaze-server" % "0.23.11", 21 | "org.http4s" %% "http4s-blaze-client" % "0.23.11", 22 | "org.http4s" %% "http4s-circe" % "0.23.11", 23 | "ch.qos.logback" % "logback-classic" % "1.2.11", 24 | "org.scalatest" %% "scalatest" % "3.2.12" % Test, 25 | "org.typelevel" %% "cats-laws" % "2.7.0" % Test, 26 | "org.typelevel" %% "discipline-scalatest" % "2.1.5" % Test, 27 | "org.typelevel" %% "cats-effect-testing-scalatest" % "1.4.0" % Test 28 | ) 29 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/celsius.txt: -------------------------------------------------------------------------------- 1 | 100.55555555555556 2 | 0.0 3 | 37.77777777777778 4 | -17.77777777777778 -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/fahrenheit.txt: -------------------------------------------------------------------------------- 1 | 213 2 | // 3 3 | 32 4 | 100 5 | 0 6 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") 2 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | myApplication.dVersion = "d2" 2 | -------------------------------------------------------------------------------- /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] { case _ => 17 | IO(Response(Status.Ok)) 18 | } 19 | 20 | val helloRoutes = HttpRoutes.of[IO] { 21 | case GET -> Root / "hello" / name => // GET example.com/hello/zdravko 22 | Ok(s"Hello, $name.") 23 | case GET -> Root / "hola" / name => 24 | Ok(s"¡Hola, $name!") 25 | } 26 | 27 | val combined = helloRoutes <+> mostSimpleRoute 28 | 29 | // Or define them under different path prefixes like that 30 | val combinedWithRouter = Router("/" -> helloRoutes, "/simple" -> mostSimpleRoute) 31 | 32 | val httpApp: HttpApp[IO] = combinedWithRouter.orNotFound 33 | 34 | object HelloWorldApp extends IOApp: 35 | def run(args: List[String]): IO[ExitCode] = 36 | BlazeServerBuilder[IO] 37 | .bindHttp(8080, "localhost") 38 | .withHttpApp(Example1_HttpRoutes.httpApp) 39 | .resource 40 | .use(_ => IO.never) 41 | .as(ExitCode.Success) 42 | -------------------------------------------------------------------------------- /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 | @main 14 | def run = 15 | 16 | // Match rest of the path 17 | val app = HttpRoutes.of[IO] { 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 | def getUserName(userId: Int): IO[String] = IO.pure(s"User$userId") 24 | 25 | val usersService = HttpRoutes.of[IO] { 26 | case GET -> Root / "users" / IntVar(userId) => 27 | Ok(getUserName(userId)) 28 | case GET -> Root / "usersLong" / LongVar(_) => 29 | ??? 30 | case GET -> Root / "usersUUID" / UUIDVar(_) => 31 | ??? 32 | } 33 | 34 | println(requestEntityUnsafe[String](usersService)(get(uri"/users/1"))) 35 | println(requestUnsafe(usersService)(get(uri"/users/two"))) 36 | 37 | /** Custom extractors We need: def unapply(str: String): Option[T] 38 | */ 39 | 40 | object LocalDateVar: 41 | def unapply(str: String): Option[LocalDate] = 42 | if str.nonEmpty then Try(LocalDate.parse(str)).toOption 43 | else None 44 | 45 | def getTemperatureForecast(date: LocalDate): IO[Double] = IO.println(date) *> IO(42.23) 46 | 47 | val dailyWeatherService = HttpRoutes.of[IO] { 48 | case GET -> Root / "weather" / "temperature" / LocalDateVar(localDate) => 49 | Ok(getTemperatureForecast(localDate).map(s"The temperature on $localDate will be: " + _)) 50 | } 51 | 52 | println(requestEntityUnsafe[String](dailyWeatherService)(get(uri"/weather/temperature/2016-11-05"))) 53 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | def requestEntityUnsafe[T](routes: HttpRoutes[IO])(request: Request[IO])(implicit d: EntityDecoder[IO, T]): T = 14 | routes.orNotFound.run(request).flatMap(_.as[T]).unsafeRunSync() 15 | 16 | def printResponseAndEntity[T]( 17 | routes: HttpRoutes[IO] 18 | )( 19 | request: Request[IO] 20 | )(implicit d: EntityDecoder[IO, T] 21 | ): IO[Response[IO]] = for 22 | resp <- routes.orNotFound.run(request) 23 | _ <- IO.println(resp) 24 | _ <- IO.println(resp.as[T]) 25 | yield resp 26 | -------------------------------------------------------------------------------- /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 org.http4s.circe.{jsonEncoderOf, jsonOf} 5 | import org.http4s.{EntityDecoder, EntityEncoder} 6 | 7 | import io.circe.Codec 8 | import io.circe.parser.* 9 | import io.circe.syntax.* 10 | 11 | case class Joke(joke: String) 12 | 13 | object Joke: 14 | import io.circe.generic.semiauto.* 15 | 16 | given Codec[Joke] = deriveCodec[Joke] 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.uri 7 | import org.http4s.Uri 8 | 9 | import org.http4s.circe.CirceEntityCodec.circeEntityDecoder 10 | 11 | final case class JokeError(e: Throwable) extends RuntimeException 12 | 13 | class JokeService(client: Client[IO]): 14 | def getJoke: IO[Joke] = 15 | client 16 | .expect[Joke](Request[IO](Method.GET, uri"https://icanhazdadjoke.com/")) 17 | .handleErrorWith(t => IO.raiseError(JokeError(t))) 18 | -------------------------------------------------------------------------------- /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].resource 13 | 14 | jokeService = new JokeService(client) 15 | jokeRouter = new JokeRouter(jokeService) 16 | 17 | server <- BlazeServerBuilder[IO] 18 | .bindHttp(8080, "localhost") 19 | .withHttpApp(jokeRouter.httpApp) 20 | .resource 21 | yield (client, server) 22 | 23 | appResource 24 | .use(_ => IO.never) 25 | .as(ExitCode.Success) 26 | -------------------------------------------------------------------------------- /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 | object SimpleMiddlewareExample: 10 | def addHeaderMiddleware(service: HttpRoutes[IO], header: Header.ToRaw): HttpRoutes[IO] = Kleisli { 11 | (req: Request[IO]) => 12 | service(req).map { 13 | case Status.Successful(resp) => 14 | resp.putHeaders(header) 15 | case resp => 16 | resp 17 | } 18 | } 19 | 20 | val service: HttpRoutes[IO] = HttpRoutes.of[IO] { 21 | case GET -> Root / "bad" => 22 | BadRequest() 23 | case _ => 24 | Ok() 25 | } 26 | 27 | val wrappedService: HttpRoutes[IO] = addHeaderMiddleware(service, "SomeKey" -> "SomeValue") 28 | 29 | object SimpleMiddlewareTest extends IOApp: 30 | 31 | import SimpleMiddlewareExample.* 32 | 33 | def run(args: List[String]): IO[ExitCode] = 34 | val goodRequest = Request[IO](Method.GET, uri"/") 35 | val badRequest = Request[IO](Method.GET, uri"/bad") 36 | 37 | for 38 | resp1 <- wrappedService.orNotFound(goodRequest) 39 | _ <- IO.println(resp1) 40 | resp2 <- wrappedService.orNotFound(badRequest) 41 | _ <- IO.println(resp2) 42 | yield ExitCode.Success 43 | -------------------------------------------------------------------------------- /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 | def login(userId: Int): IO[Response[IO]] = 13 | Ok("Logged in!").map(_.addCookie(ResponseCookie("authcookie", userId.toString))) 14 | 15 | def retrieveUser(id: Int) = 16 | UserDatabase(id).map(_.toRight("User not found")) 17 | 18 | val authUser: Kleisli[IO, Request[IO], Either[String, User]] = 19 | Kleisli { request => 20 | val eitherUserId = for 21 | cookie <- request.cookies.find(_.name == "authcookie").toRight("Cookie parsing error") 22 | userId <- Either.catchOnly[NumberFormatException](cookie.content.toInt).leftMap(_.toString) 23 | yield userId 24 | 25 | eitherUserId.fold( 26 | error => IO.pure(Left(error)), 27 | retrieveUser 28 | ) 29 | } 30 | 31 | val onFailure: AuthedRoutes[String, IO] = Kleisli(req => OptionT.liftF(Forbidden(req.context))) 32 | val middleware: AuthMiddleware[IO, User] = AuthMiddleware(authUser, onFailure) 33 | -------------------------------------------------------------------------------- /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] { case POST -> Root / "login" :? UserIdQueryParam(userId) => 14 | Auth.login(userId) 15 | } 16 | 17 | val authedRoutes: AuthedRoutes[User, IO] = 18 | AuthedRoutes.of { case GET -> Root / "welcome" as user => 19 | Ok(s"Welcome, ${user.name}") 20 | } 21 | 22 | val httpApp = Router( 23 | "/" -> unprotectedRoutes, 24 | "/protected" -> Auth.middleware(authedRoutes) 25 | ).orNotFound 26 | -------------------------------------------------------------------------------- /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] 12 | .bindHttp(8080, "localhost") 13 | .withHttpApp(AuthService.httpApp) 14 | .resource 15 | .use(_ => IO.never) 16 | .as(ExitCode.Success) 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | object GZipMiddlewareExample extends IOApp: 11 | val service: HttpRoutes[IO] = HttpRoutes.of[IO] { case _ => 12 | Ok("I repeat myself when I'm under stress. " * 3) 13 | } 14 | 15 | val zipService = GZip(service) 16 | 17 | override def run(args: List[String]): IO[ExitCode] = 18 | val request = Request[IO](Method.GET, uri"/") 19 | 20 | val acceptHeader = `Accept-Encoding`(ContentCoding.gzip) 21 | val acceptGZipRequest = request.putHeaders(acceptHeader) 22 | 23 | for 24 | resp1 <- zipService.orNotFound(request) 25 | body1 <- resp1.as[String] 26 | _ <- IO.println(resp1) 27 | _ <- IO.println(body1) 28 | resp2 <- zipService.orNotFound(acceptGZipRequest) 29 | body2 <- resp2.as[String] 30 | _ <- IO.println(resp2) 31 | _ <- IO.println(body2) 32 | yield ExitCode.Success 33 | -------------------------------------------------------------------------------- /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 | case class Tweet(id: Int, content: String, likes: Int = 0) 8 | 9 | object Tweet: 10 | import io.circe.generic.semiauto.* 11 | import org.http4s.circe.* 12 | 13 | given Codec[Tweet] = deriveCodec 14 | 15 | implicit def tweetEntityEncoder: EntityEncoder[IO, Tweet] = jsonEncoderOf[IO, Tweet] 16 | implicit def tweetsEntityEncoder: EntityEncoder[IO, Seq[Tweet]] = jsonEncoderOf[IO, Seq[Tweet]] 17 | implicit def tweetEntityDecoder: EntityDecoder[IO, Tweet] = jsonOf[IO, Tweet] 18 | -------------------------------------------------------------------------------- /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 | given Codec[Tweet] = deriveCodec 13 | 14 | object DerivedCodecExample extends App: 15 | val tweet = Tweet(1, "Some random content", 123124) 16 | 17 | println(tweet.asJson) 18 | 19 | val json = 20 | """ 21 | |{ 22 | | "id" : 1, 23 | | "content" : "Some random content", 24 | | "likes" : 123124 25 | |}""".stripMargin 26 | 27 | val decodedTweet = decode[Tweet](json) 28 | println(decodedTweet) 29 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitycomposition/MyApplication.scala: -------------------------------------------------------------------------------- 1 | package modularitycomposition 2 | 3 | import com.typesafe.config.{Config, ConfigFactory} 4 | import modularitycomposition.a.{A1, A2, A3, AModule} 5 | import modularitycomposition.b.{B1, B2, BModule} 6 | import modularitycomposition.c.{C, CModule} 7 | import modularitycomposition.d.DModule 8 | 9 | object MyApplication: 10 | val config: Config = ConfigFactory.load() 11 | 12 | val aModule = new AModule 13 | val bModule = new BModule(aModule.a1) 14 | val cModule = new CModule(aModule.a3, bModule.b2) 15 | val dModule = new DModule(config) 16 | 17 | def main(args: Array[String]): Unit = 18 | cModule.c.doSomething() 19 | println(dModule.d) 20 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitycomposition/a/A1.scala: -------------------------------------------------------------------------------- 1 | package modularitycomposition.a 2 | 3 | class A1 4 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitycomposition/a/A2.scala: -------------------------------------------------------------------------------- 1 | package modularitycomposition.a 2 | 3 | class A2 4 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitycomposition/a/A3.scala: -------------------------------------------------------------------------------- 1 | package modularitycomposition.a 2 | 3 | class A3(a1: A1, a2: A2) -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitycomposition/a/AModule.scala: -------------------------------------------------------------------------------- 1 | package modularitycomposition.a 2 | 3 | class AModule: 4 | val a1 = new A1 5 | val a2 = new A2 6 | val a3 = new A3(a1, a2) 7 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitycomposition/b/B1.scala: -------------------------------------------------------------------------------- 1 | package modularitycomposition.b 2 | 3 | class B1 4 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitycomposition/b/B2.scala: -------------------------------------------------------------------------------- 1 | package modularitycomposition.b 2 | 3 | import modularitycomposition.a.A1 4 | 5 | class B2(b1: B1, a1: A1) 6 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitycomposition/b/BModule.scala: -------------------------------------------------------------------------------- 1 | package modularitycomposition.b 2 | 3 | import modularitycomposition.a.A1 4 | 5 | class BModule(a1: A1): 6 | val b1 = new B1 7 | val b2 = new B2(b1, a1) 8 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitycomposition/c/C.scala: -------------------------------------------------------------------------------- 1 | package modularitycomposition.c 2 | 3 | import modularitycomposition.a.A3 4 | import modularitycomposition.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/modularitycomposition/c/CModule.scala: -------------------------------------------------------------------------------- 1 | package modularitycomposition.c 2 | 3 | import modularitycomposition.a.A3 4 | import modularitycomposition.b.{B2, BModule} 5 | 6 | class CModule(a3: A3, b2: B2): 7 | val c = new C(a3, b2) 8 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitycomposition/d/D.scala: -------------------------------------------------------------------------------- 1 | package modularitycomposition.d 2 | 3 | import com.typesafe.config.Config 4 | 5 | trait D 6 | class D1 extends D 7 | class D2 extends D 8 | 9 | class DModule(config: Config): 10 | val d: D = 11 | if config.getString("myApplication.dVersion") == "d1" then new D1 12 | else new D2 13 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitythincake/MyApplication.scala: -------------------------------------------------------------------------------- 1 | package modularitythincake 2 | 3 | import com.typesafe.config.{Config, ConfigFactory} 4 | import modularitythincake.a.{A1, A2, A3, AModule} 5 | import modularitythincake.b.{B1, B2, BModule} 6 | import modularitythincake.c.{C, CModule} 7 | import modularitythincake.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 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitythincake/a/A1.scala: -------------------------------------------------------------------------------- 1 | package modularitythincake.a 2 | 3 | class A1 4 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitythincake/a/A2.scala: -------------------------------------------------------------------------------- 1 | package modularitythincake.a 2 | 3 | class A2 4 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitythincake/a/A3.scala: -------------------------------------------------------------------------------- 1 | package modularitythincake.a 2 | 3 | class A3(a1: A1, a2: A2): 4 | println((a1, a2)) 5 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitythincake/a/AModule.scala: -------------------------------------------------------------------------------- 1 | package modularitythincake.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 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitythincake/b/B1.scala: -------------------------------------------------------------------------------- 1 | package modularitythincake.b 2 | 3 | class B1 4 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitythincake/b/B2.scala: -------------------------------------------------------------------------------- 1 | package modularitythincake.b 2 | 3 | import modularitythincake.a.A1 4 | 5 | class B2(b1: B1, a1: A1) 6 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitythincake/b/BModule.scala: -------------------------------------------------------------------------------- 1 | package modularitythincake.b 2 | 3 | import modularitythincake.a.A1 4 | 5 | trait BModule: 6 | def a1: A1 7 | 8 | lazy val b1 = 9 | new B1 10 | lazy val b2 = new B2(b1, a1) 11 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitythincake/c/C.scala: -------------------------------------------------------------------------------- 1 | package modularitythincake.c 2 | 3 | import modularitythincake.a.A3 4 | import modularitythincake.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/modularitythincake/c/CModule.scala: -------------------------------------------------------------------------------- 1 | package modularitythincake.c 2 | 3 | import modularitythincake.a.A3 4 | import modularitythincake.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 | -------------------------------------------------------------------------------- /lectures/examples/12-building-a-scala-app/src/main/scala/modularitythincake/d/D.scala: -------------------------------------------------------------------------------- 1 | package modularitythincake.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" then new D1 14 | else new D2 15 | -------------------------------------------------------------------------------- /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].to[List] 11 | 12 | val randomSelect = sql"SELECT random()".query[Double].unique 13 | val ex3 = (ex2, randomSelect).tupled 14 | val ex4 = randomSelect.replicateA(10) 15 | -------------------------------------------------------------------------------- /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 | given Meta[Email] = Meta[String].imap(_.split("@") match 29 | case Array(name, domain) => Email(name, domain) 30 | )(e => s"${e.name}@${e.domain}") 31 | 32 | val ex3 = sql"SELECT 'viktor@gmail.com'".query[Email].unique 33 | -------------------------------------------------------------------------------- /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 | def populationInRange(range: Range) = 17 | selectCountries(fr"${range.min} < population AND population < ${range.max}") 18 | 19 | val ex1 = populationInRange(100000000 to 300000000).to[List] 20 | 21 | def countriesByCodes(codes: NonEmptyList[String]) = 22 | selectCountries(Fragments.in(fr"code", codes)) 23 | 24 | val ex2 = countriesByCodes(NonEmptyList.of("USA", "BRA", "PAK", "GBR")).to[List] 25 | -------------------------------------------------------------------------------- /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 | val data = List[Person]( 15 | Person(10, "Maya", Some(24)), 16 | Person(20, "Ivan", None) 17 | ) 18 | 19 | val insertData = insertMany(data) 20 | 21 | def run: IO[Unit] = 22 | setupPersonTable.transact(dbTransactor) >> 23 | insertData.transact(dbTransactor) >>= 24 | IO.println 25 | -------------------------------------------------------------------------------- /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 | "password" 14 | ) 15 | 16 | def run: IO[Unit] = Doobie03Fragments.ex2.transact(dbTransactor) >>= IO.println 17 | -------------------------------------------------------------------------------- /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 | println(repeat(s3).take(10).toList) 22 | 23 | val effectfulStream = Stream.eval(IO.println("Hellou!!!")) 24 | println(effectfulStream.compile.drain.unsafeRunSync()) 25 | 26 | val effectfulStream2 = Stream.evalSeq(IO(List(1, 2, 3))) 27 | println(effectfulStream2.compile.fold(0)(_ + _).unsafeRunSync()) 28 | -------------------------------------------------------------------------------- /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.io.file.Path 6 | import fs2.{Stream, text} 7 | 8 | import java.nio.file.Paths 9 | 10 | import cats.syntax.all.* 11 | 12 | object Fs202Files extends IOApp.Simple: 13 | def converter(inputFile: String, outputFile: String): Stream[IO, String] = 14 | def fahrenheitToCelsius(f: Double): Double = 15 | (f - 32.0) * (5.0 / 9.0) 16 | 17 | Files[IO] 18 | .readAll(Path(inputFile)) 19 | .through(text.utf8.decode) 20 | .through(text.lines) 21 | .filter(s => s.trim.nonEmpty && !s.startsWith("//")) 22 | .map(line => fahrenheitToCelsius(line.toDouble).toString) 23 | .intersperse("\n") 24 | .through(text.utf8.encode) 25 | .through(Files[IO].writeAll(Path(outputFile))) 26 | 27 | def run: IO[Unit] = converter("fahrenheit.txt", "celsius.txt").compile.toList >>= IO.println 28 | -------------------------------------------------------------------------------- /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.duration.DurationInt 11 | 12 | object Fs203Http extends IOApp.Simple: 13 | val countToTen: Stream[IO, String] = 14 | Stream 15 | .awakeEvery[IO](1.second) 16 | .map(_.toString + "\n") 17 | .take(10) 18 | 19 | val counterRoutes = HttpRoutes.of[IO] { case GET -> Root / "counter" => 20 | Ok(countToTen) 21 | } 22 | 23 | val httpApp = counterRoutes.orNotFound 24 | 25 | val serverBuilder = BlazeServerBuilder[IO] 26 | .bindHttp(8080, "localhost") 27 | .withHttpApp(httpApp) 28 | .resource 29 | 30 | def run: IO[Unit] = serverBuilder.use(_ => IO.never) 31 | -------------------------------------------------------------------------------- /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.duration.DurationInt 15 | 16 | case class City(name: String, country: String, population: Int) 17 | 18 | object Fs204HttpWithDoobie extends IOApp.Simple: 19 | val allCities = 20 | sql""" 21 | SELECT name, countrycode, population 22 | FROM city 23 | """ 24 | .query[City] 25 | .stream 26 | .transact(DoobieApp.dbTransactor) 27 | 28 | val count: Stream[IO, String] = 29 | Stream 30 | .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] 49 | .bindHttp(8080, "localhost") 50 | .withHttpApp(httpApp) 51 | .resource 52 | 53 | def run: IO[Unit] = serverBuilder.use(_ => IO.never) 54 | -------------------------------------------------------------------------------- /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, WebSocketBuilder2} 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(wsBuilder: WebSocketBuilder2[IO]): HttpRoutes[IO] = HttpRoutes.of[IO] { 17 | case GET -> Root / "echo-ws" => 18 | val echoReply: Pipe[IO, WebSocketFrame, WebSocketFrame] = 19 | _.flatMap { 20 | case Text(msg, _) => 21 | Stream( 22 | Text(s"You sent the server: $msg."), 23 | Text("Yay :)") 24 | ) 25 | case _ => Stream(Text("You sent something different than text")) 26 | } 27 | 28 | wsBuilder.build(echoReply) 29 | } 30 | 31 | def httpApp(wsBuilder: WebSocketBuilder2[IO]) = routes(wsBuilder).orNotFound 32 | 33 | val serverBuilder = BlazeServerBuilder[IO] 34 | .bindHttp(8080, "localhost") 35 | .withHttpWebSocketApp(httpApp) 36 | .resource 37 | 38 | def run: IO[Unit] = serverBuilder.use(_ => IO.never) 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-library-app/.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.5.1 2 | 3 | runner.dialect = scala3 4 | 5 | maxColumn = 120 6 | 7 | indent.defnSite = 2 8 | 9 | verticalMultiline.newlineAfterOpenParen = true 10 | verticalMultiline.arityThreshold = 5 11 | verticalMultiline.atDefnSite = true 12 | danglingParentheses.exclude = [] 13 | 14 | align.preset = none 15 | assumeStandardLibraryStripMargin = true 16 | align.stripMargin = true 17 | 18 | rewrite.scala3.convertToNewSyntax = true 19 | rewrite.scala3.removeOptionalBraces = true 20 | 21 | spaces.beforeContextBoundColon = Always 22 | 23 | rewrite { 24 | rules = [ 25 | RedundantParens, 26 | SortModifiers 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /lectures/examples/12-library-app/build.sbt: -------------------------------------------------------------------------------- 1 | name := "library-app" 2 | version := "0.1" 3 | 4 | scalaVersion := "3.1.1" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.typelevel" %% "cats-core" % "2.7.0", 8 | "org.typelevel" %% "cats-effect" % "3.3.12", 9 | 10 | "org.http4s" %% "http4s-dsl" % "0.23.11", 11 | "org.http4s" %% "http4s-blaze-server" % "0.23.11", 12 | "org.http4s" %% "http4s-blaze-client" % "0.23.11", 13 | "org.http4s" %% "http4s-circe" % "0.23.11", 14 | 15 | "io.circe" %% "circe-generic" % "0.14.2", 16 | 17 | "ch.qos.logback" % "logback-classic" % "1.2.11", 18 | 19 | "org.scalatest" %% "scalatest" % "3.2.12" % Test 20 | ) 21 | -------------------------------------------------------------------------------- /lectures/examples/12-library-app/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /lectures/examples/12-library-app/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") 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.syntax.all.* 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 | client <- BlazeClientBuilder[IO].resource 15 | 16 | libraryApi = new LibraryApi(client) 17 | libraryClientUI = new LibraryClientUI(libraryApi) 18 | yield libraryClientUI 19 | 20 | def run: IO[Unit] = 21 | app 22 | .use(ui => ui.selectBook) 23 | .onCancel(IO.println("Thank you for browsing our library :)!")) 24 | -------------------------------------------------------------------------------- /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.syntax.all.* 6 | import fmi.server.LibraryHttpApp 7 | import org.http4s.blaze.server.BlazeServerBuilder 8 | 9 | object LibraryServer extends IOApp.Simple: 10 | val server = 11 | BlazeServerBuilder[IO] 12 | .bindHttp(8080, "localhost") 13 | .withHttpApp(LibraryHttpApp.libraryApp) 14 | .resource 15 | 16 | def run: IO[Unit] = 17 | server 18 | .use(_ => IO.never) 19 | .onCancel(IO.println("Bye, see you again \uD83D\uDE0A")) 20 | -------------------------------------------------------------------------------- /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.syntax.all.* 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.given 12 | import org.http4s.circe.CirceEntityCodec.given 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 | -------------------------------------------------------------------------------- /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 | import io.circe.Encoder 6 | import io.circe.Decoder 7 | 8 | object LibraryCodecs: 9 | import io.circe.generic.semiauto.* 10 | 11 | given Codec[String] = Codec.from(Decoder.decodeString, Encoder.encodeString) 12 | 13 | given Codec[AuthorId] = Codec[String].iemap(id => Right(AuthorId(id)))(_.id) 14 | given Codec[Author] = deriveCodec 15 | 16 | given Codec[BookId] = Codec[String].iemap(id => Right(BookId(id)))(_.id) 17 | given Codec[Book] = deriveCodec 18 | 19 | given Codec[BookSummary] = deriveCodec 20 | -------------------------------------------------------------------------------- /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 | case class BookSummary(id: BookId, name: String) 7 | -------------------------------------------------------------------------------- /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) 6 | case class Book(id: BookId, name: String, authors: List[AuthorId], genre: String) 7 | 8 | case class AuthorId(id: String) 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 | object Library: 22 | private val books = List( 23 | Book(BookId("1"), "Programming in Scala", List(AuthorId("1"), AuthorId("2")), "Computer Science"), 24 | Book(BookId("2"), "Programming Erlang", List(AuthorId("3")), "Computer Science"), 25 | Book(BookId("3"), "American Gods", List(AuthorId("4")), "Fantasy"), 26 | Book(BookId("4"), "The Fellowship of the Ring", List(AuthorId("5")), "Fantasy"), 27 | Book( 28 | BookId("5"), 29 | "The Book", 30 | List( 31 | AuthorId("1"), 32 | AuthorId("3"), 33 | AuthorId("4"), 34 | AuthorId("5") 35 | ), 36 | "Fantasy" 37 | ) 38 | ) 39 | private val authors = List( 40 | Author(AuthorId("1"), "Martin Odersky"), 41 | Author(AuthorId("2"), "Bill Venners"), 42 | Author(AuthorId("3"), "Joe Armstrong"), 43 | Author(AuthorId("4"), "Neil Gaiman"), 44 | Author(AuthorId("5"), "J. R. R. Tolkien") 45 | ) 46 | 47 | val TheGreatLibrary = new Library(books, authors) 48 | -------------------------------------------------------------------------------- /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.syntax.all.* 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.given 13 | import org.http4s.circe.CirceEntityCodec.given 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 24 | .findBook(bookId) 25 | .flatMap( 26 | _.fold(NotFound())(book => Ok(book)) 27 | ) 28 | } 29 | 30 | val authorRoutes = HttpRoutes.of[IO] { case GET -> Root / "authors" / authorIdSegment => 31 | val authorId = AuthorId(authorIdSegment) 32 | 33 | TheGreatLibrary 34 | .findAuthor(authorId) 35 | .flatMap( 36 | _.fold(NotFound())(author => Ok(author)) 37 | ) 38 | } 39 | 40 | val libraryApp = (bookRoutes <+> authorRoutes).orNotFound 41 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.5.1 2 | 3 | runner.dialect = scala3 4 | 5 | maxColumn = 120 6 | 7 | indent.defnSite = 2 8 | 9 | verticalMultiline.newlineAfterOpenParen = true 10 | verticalMultiline.arityThreshold = 5 11 | verticalMultiline.atDefnSite = true 12 | danglingParentheses.exclude = [] 13 | 14 | align.preset = none 15 | assumeStandardLibraryStripMargin = true 16 | align.stripMargin = true 17 | 18 | rewrite.scala3.convertToNewSyntax = true 19 | rewrite.scala3.removeOptionalBraces = true 20 | 21 | spaces.beforeContextBoundColon = Always 22 | 23 | rewrite { 24 | rules = [ 25 | RedundantParens, 26 | SortModifiers 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/README.md: -------------------------------------------------------------------------------- 1 | # Shopping Application 2 | 3 | # DB Setup 4 | 5 | Install PostgreSQL. Once installed go into `psql` from the command line (instructions should be available depending on you method of installation) and enter: 6 | 7 | ``` 8 | CREATE DATABASE shoppingapp; 9 | CREATE USER shoppingapp WITH ENCRYPTED PASSWORD 'secret-P@assw0rd'; 10 | GRANT ALL PRIVILEGES ON DATABASE shoppingapp TO shoppingapp; 11 | ``` 12 | 13 | # Run application 14 | 15 | The application can be started with `sbt run`. 16 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/build.sbt: -------------------------------------------------------------------------------- 1 | name := "shopping-app" 2 | version := "0.1" 3 | 4 | scalaVersion := "3.1.1" 5 | 6 | libraryDependencies ++= Seq( 7 | "org.typelevel" %% "cats-core" % "2.7.0", 8 | "org.typelevel" %% "cats-effect" % "3.3.12", 9 | 10 | "co.fs2" %% "fs2-core" % "3.2.7", 11 | "co.fs2" %% "fs2-io" % "3.2.7", 12 | "co.fs2" %% "fs2-reactive-streams" % "3.2.7", 13 | 14 | "org.http4s" %% "http4s-dsl" % "0.23.11", 15 | "org.http4s" %% "http4s-blaze-server" % "0.23.11", 16 | "org.http4s" %% "http4s-blaze-client" % "0.23.11", 17 | "org.http4s" %% "http4s-circe" % "0.23.11", 18 | 19 | "org.flywaydb" % "flyway-core" % "8.5.12", 20 | 21 | "org.tpolecat" %% "doobie-core" % "1.0.0-RC2", 22 | "org.tpolecat" %% "doobie-hikari" % "1.0.0-RC2", 23 | "org.tpolecat" %% "doobie-postgres" % "1.0.0-RC2", 24 | 25 | "com.typesafe" % "config" % "1.4.2", 26 | 27 | "io.circe" %% "circe-generic" % "0.14.2", 28 | // "io.circe" %% "circe-generic-extras" % "0.14.2", 29 | // "io.circe" %% "circe-config" % "0.8.0", 30 | 31 | "org.mindrot" % "jbcrypt" % "0.3m", 32 | 33 | ("org.reactormonk" %% "cryptobits" % "1.3").cross(CrossVersion.for3Use2_13), 34 | 35 | "ch.qos.logback" % "logback-classic" % "1.2.11", 36 | 37 | "org.scalatest" %% "scalatest" % "3.2.12" % Test, 38 | "org.typelevel" %% "cats-laws" % "2.7.0" % Test, 39 | "org.typelevel" %% "discipline-scalatest" % "2.1.5" % Test, 40 | "org.typelevel" %% "cats-effect-testing-scalatest" % "1.4.0" % Test 41 | ) 42 | 43 | fork := true 44 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") 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 | user = "shoppingapp" 12 | password = "secret-P@assw0rd" 13 | name = "shoppingapp" 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.syntax.all.* 6 | import com.typesafe.config.ConfigFactory 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 org.http4s.HttpRoutes 14 | import org.http4s.blaze.server.BlazeServerBuilder 15 | import org.http4s.implicits.* 16 | import org.http4s.server.Server 17 | 18 | object ShoppingApp extends IOApp.Simple: 19 | val app: Resource[IO, Server] = for 20 | config <- Resource 21 | .eval(IO.blocking(ConfigFactory.load())) 22 | .map(_.getConfig("shoppingApp")) 23 | .map(ShoppingAppConfig.fromConfig) 24 | 25 | cryptoService = new CryptoService(config.secretKey) 26 | 27 | dbModule <- DbModule(config.database) 28 | 29 | usersModule <- UsersModule(dbModule.dbTransactor, cryptoService) 30 | inventoryModule <- InventoryModule(dbModule.dbTransactor) 31 | shoppingModule <- ShoppingModule(dbModule.dbTransactor, inventoryModule.productStockDao) 32 | 33 | nonAuthenticatedRoutes = usersModule.routes <+> inventoryModule.routes 34 | authenticatedRoutes = usersModule.authMiddleware { 35 | usersModule.authenticatedRoutes <+> inventoryModule.authenticatedRoutes <+> shoppingModule.authenticatedRoutes 36 | } 37 | 38 | routes = (nonAuthenticatedRoutes <+> authenticatedRoutes).orNotFound 39 | 40 | httpServer <- BlazeServerBuilder[IO] 41 | .bindHttp(config.http.port, "localhost") 42 | .withHttpApp(routes) 43 | .resource 44 | yield httpServer 45 | 46 | def run: IO[Unit] = 47 | app 48 | .use(_ => IO.never) 49 | .onCancel(IO.println("Bye, see you again \uD83D\uDE0A")) 50 | -------------------------------------------------------------------------------- /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 | import com.typesafe.config.Config 6 | 7 | case class ShoppingAppConfig( 8 | secretKey: String, 9 | http: HttpConfig, 10 | database: DbConfig 11 | ) 12 | 13 | object ShoppingAppConfig: 14 | def fromConfig(config: Config): ShoppingAppConfig = 15 | val secretKey = config.getString("secretKey") 16 | 17 | val http = HttpConfig( 18 | config.getInt("http.port") 19 | ) 20 | 21 | val dbConfig = config.getConfig("database") 22 | val database = DbConfig( 23 | dbConfig.getString("host"), 24 | dbConfig.getInt("port"), 25 | dbConfig.getString("user"), 26 | dbConfig.getString("password"), 27 | dbConfig.getString("name"), 28 | dbConfig.getString("schema"), 29 | dbConfig.getInt("connectionPoolSize") 30 | ) 31 | 32 | ShoppingAppConfig( 33 | secretKey, 34 | http, 35 | database 36 | ) 37 | -------------------------------------------------------------------------------- /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 | def decrypt(str: String): Option[String] = 13 | crypto.validateSignedToken(str) 14 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/infrastructure/db/DbConfig.scala: -------------------------------------------------------------------------------- 1 | package fmi.infrastructure.db 2 | 3 | case class DbConfig( 4 | host: String, 5 | port: Int, 6 | user: String, 7 | password: String, 8 | name: String, 9 | schema: String, 10 | connectionPoolSize: Int 11 | ): 12 | def jdbcUrl: String = s"jdbc:postgresql://$host:$port/$name" 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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( 23 | InventoryModule( 24 | productDao, 25 | productStockDao, 26 | inventoryRouter.nonAuthenticatedRoutes, 27 | inventoryRouter.authenticatedRoutes 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.syntax.all.* 4 | import fmi.user.{AuthenticatedUser, UserRole} 5 | import fmi.utils.CirceUtils 6 | import io.circe.Codec 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.given 13 | import org.http4s.circe.CirceEntityCodec.given 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 23 | .retrieveProduct(productSku) 24 | .flatMap( 25 | _.fold(NotFound())(Ok(_)) 26 | ) 27 | } 28 | 29 | def authenticatedRoutes: AuthedRoutes[AuthenticatedUser, IO] = AuthedRoutes.of[AuthenticatedUser, IO] { 30 | case authReq @ POST -> Root / "products" as user if user.role == UserRole.Admin => 31 | Ok(authReq.req.as[Product] >>= productDao.addProduct) 32 | 33 | case authReq @ POST -> Root / "stock" as user if user.role == UserRole.Admin => 34 | val adjustmentResult = authReq.req.as[InventoryAdjustment] >>= productStockDao.applyInventoryAdjustment 35 | 36 | adjustmentResult.flatMap { 37 | case SuccessfulAdjustment => Ok() 38 | case NotEnoughStockAvailable => Conflict() 39 | } 40 | } 41 | 42 | object InventoryJsonCodecs: 43 | given Codec[ProductSku] = CirceUtils.unwrappedCodec(ProductSku.apply)(_.sku) 44 | given Codec[Product] = deriveCodec 45 | given Codec[ProductStock] = deriveCodec 46 | 47 | given Codec[ProductStockAdjustment] = deriveCodec 48 | given Codec[InventoryAdjustment] = deriveCodec 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.syntax.all.* 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 | def addProduct(product: Product): IO[Unit] = 20 | sql""" 21 | INSERT INTO product as p (sku, name, description, weight_in_grams) 22 | VALUES (${product.sku}, ${product.name}, ${product.description}, ${product.weightInGrams}) 23 | ON CONFLICT (sku) DO UPDATE SET 24 | name = EXCLUDED.name, 25 | description = EXCLUDED.description, 26 | weight_in_grams = EXCLUDED.weight_in_grams 27 | """.update.run.void 28 | .transact(dbTransactor) 29 | -------------------------------------------------------------------------------- /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) 12 | 13 | case class OrderLine(product: ProductSku, quantity: Int): 14 | def toProductStockAdjustment = ProductStockAdjustment(product, -quantity) 15 | 16 | object OrderId: 17 | def generate: IO[OrderId] = IO(OrderId(UUID.randomUUID().toString)) 18 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/shopping/OrderDao.scala: -------------------------------------------------------------------------------- 1 | package fmi.shopping 2 | 3 | import cats.syntax.all.* 4 | import doobie.* 5 | import doobie.implicits.* 6 | import doobie.util.meta.LegacyInstantMetaInstance 7 | import fmi.infrastructure.db.DoobieDatabase.DbTransactor 8 | 9 | class OrderDao(dbTransactor: DbTransactor) extends LegacyInstantMetaInstance: 10 | def placeOrder(order: Order): ConnectionIO[Order] = 11 | val insertOrder = sql""" 12 | INSERT INTO "order" (id, user_id, placing_timestamp) 13 | VALUES (${order.orderId}, ${order.user}, ${order.placingTimestamp}) 14 | """ 15 | 16 | val insertOrderLine = 17 | val orderLinesInsert = """ 18 | INSERT INTO order_line(order_id, sku, quantity) 19 | VALUES(?, ?, ?) 20 | """ 21 | 22 | Update[(OrderId, OrderLine)](orderLinesInsert) 23 | .updateMany(order.orderLines.map(ol => (order.orderId, ol))) 24 | 25 | (insertOrder.update.run *> insertOrderLine).as(order) 26 | -------------------------------------------------------------------------------- /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.syntax.all.* 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( 22 | inventoryAdjustment: InventoryAdjustment, 23 | order: Order 24 | ): IO[Either[NotEnoughStockAvailable.type, Order]] = 25 | val transaction = 26 | productStockDao.applyInventoryAdjustmentAction(inventoryAdjustment) *> 27 | orderDao.placeOrder(order) 28 | 29 | transaction 30 | .transact(dbTransactor) 31 | .map(_.asRight[NotEnoughStockAvailable.type]) 32 | .recover { case NotEnoughStockAvailableException => 33 | NotEnoughStockAvailable.asLeft 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 cats.syntax.all.* 5 | import fmi.inventory.ProductSku 6 | import fmi.user.{AuthenticatedUser, UserId} 7 | import fmi.utils.CirceUtils 8 | import io.circe.Codec 9 | import io.circe.generic.semiauto.deriveCodec 10 | import org.http4s.AuthedRoutes 11 | import org.http4s.dsl.io.* 12 | 13 | class ShippingRouter(orderService: OrderService): 14 | import OrderJsonCodecs.given 15 | import org.http4s.circe.CirceEntityCodec.given 16 | 17 | def authenticatedRoutes: AuthedRoutes[AuthenticatedUser, IO] = AuthedRoutes.of[AuthenticatedUser, IO] { 18 | case authReq @ POST -> Root / "orders" as user => 19 | val placedOrder = authReq.req.as[ShoppingCart] >>= (orderService.placeOrder(user.id, _)) 20 | 21 | placedOrder.flatMap { 22 | _.fold(_ => Conflict(), Ok(_)) 23 | } 24 | } 25 | 26 | object OrderJsonCodecs: 27 | import fmi.inventory.InventoryJsonCodecs.given Codec[ProductSku] 28 | import fmi.user.UsersJsonCodecs.given Codec[UserId] 29 | 30 | given Codec[OrderLine] = deriveCodec 31 | given Codec[ShoppingCart] = deriveCodec 32 | 33 | given Codec[OrderId] = CirceUtils.unwrappedCodec(OrderId.apply)(_.id) 34 | given Codec[Order] = deriveCodec 35 | -------------------------------------------------------------------------------- /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 | def toInventoryAdjustment: InventoryAdjustment = 10 | InventoryAdjustment(orderLines.map(_.toProductStockAdjustment)) 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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) 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 | def removeUser: IO[Response[IO]] = 19 | Ok().map(_.removeCookie("loggedUser")) 20 | 21 | private val authUser: Kleisli[IO, Request[IO], Either[String, AuthenticatedUser]] = Kleisli { request => 22 | val userId = for 23 | header <- request.headers.get[Cookie].toRight("Cookie parsing error") 24 | cookie <- header.values.toList.find(_.name == "loggedUser").toRight("Not authenticated") 25 | email <- cryptoService.decrypt(cookie.content).toRight("Cookie invalid") 26 | yield UserId(email) 27 | 28 | (for 29 | userId <- EitherT.fromEither[IO](userId) 30 | user <- EitherT(usersDao.retrieveUser(userId).map(_.toRight("User not found"))) 31 | yield AuthenticatedUser(user.id, user.role)).value 32 | } 33 | 34 | private val onFailure: AuthedRoutes[String, IO] = Kleisli(req => OptionT.liftF(Forbidden(req.context))) 35 | 36 | val authMiddleware: AuthMiddleware[IO, AuthenticatedUser] = AuthMiddleware(authUser, onFailure) 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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, 7 | name: String, 8 | age: Option[Int] 9 | ) 10 | 11 | case class UserId(email: String) 12 | 13 | enum UserRole: 14 | case Admin, NormalUser 15 | -------------------------------------------------------------------------------- /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 | def validateEmail(email: String): EitherNec[RegistrationFormError, UserId] = 26 | if email.count(_ == '@') == 1 then UserId(email).rightNec 27 | else InvalidEmail(email).leftNec 28 | 29 | def validateName(name: String): EitherNec[RegistrationFormError, String] = 30 | if name.nonEmpty then name.rightNec 31 | else NameIsEmpty.leftNec 32 | 33 | def validateAge(maybeAge: Option[Int]): EitherNec[RegistrationFormError, Option[Int]] = maybeAge.map { age => 34 | if age > 0 then age.rightNec 35 | else InvalidAge(age).leftNec 36 | }.sequence 37 | -------------------------------------------------------------------------------- /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 UserDbGivens: 11 | given Meta[UserRole] = Meta[String].imap(UserRole.valueOf)(_.toString) 12 | 13 | class UsersDao(dbTransactor: DbTransactor): 14 | import UserDbGivens.given 15 | 16 | def retrieveUser(id: UserId): IO[Option[User]] = 17 | sql""" 18 | SELECT email, password_hash, role, name, age 19 | FROM "user" 20 | WHERE email = $id 21 | """ 22 | .query[User] 23 | .option 24 | .transact(dbTransactor) 25 | 26 | def registerUser(user: User): IO[Either[UserAlreadyExists, User]] = 27 | sql""" 28 | INSERT INTO "user" (email, password_hash, role, name, age) 29 | VALUES (${user.id}, ${user.passwordHash}, ${user.role}, ${user.name}, ${user.age}) 30 | """.update.run 31 | .as(user) 32 | .attemptSomeSqlState { case sqlstate.class23.UNIQUE_VIOLATION => 33 | UserAlreadyExists(user.id) 34 | } 35 | .transact(dbTransactor) 36 | 37 | def deleteUser(id: UserId): IO[Unit] = 38 | sql""" 39 | DELETE FROM user 40 | WHERE email = $id 41 | """.update.run.void 42 | .transact(dbTransactor) 43 | -------------------------------------------------------------------------------- /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( 26 | UsersModule( 27 | usersDao, 28 | usersService, 29 | authenticationUtils.authMiddleware, 30 | usersRouter.nonAuthenticatedRoutes, 31 | usersRouter.authenticatedRoutes 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.syntax.all.* 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.apply) 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) then Some(user) 22 | else None 23 | case _ => None 24 | } 25 | 26 | sealed trait RegistrationError 27 | case class UserValidationError(registrationErrors: NonEmptyChain[RegistrationFormError]) extends RegistrationError 28 | case class UserAlreadyExists(email: UserId) extends RegistrationError 29 | 30 | case class UserLogin(email: UserId, password: String) 31 | -------------------------------------------------------------------------------- /lectures/examples/12-shopping-app/src/main/scala/fmi/utils/CirceUtils.scala: -------------------------------------------------------------------------------- 1 | package fmi.utils 2 | 3 | import io.circe.Decoder.Result 4 | import io.circe.{Codec, Decoder, DecodingFailure, Encoder, HCursor, Json} 5 | 6 | import scala.util.Try 7 | 8 | case class AdtEntryCodec[A, E <: A](codec: Codec[E], clazz: Class[E]): 9 | def isApplicable(a: A) = clazz.isInstance(a) 10 | 11 | def apply(a: A): Option[Json] = 12 | if isApplicable(a) then 13 | val e = a.asInstanceOf[E] 14 | Some(codec(e)) 15 | else None 16 | 17 | def getType = clazz.getSimpleName.stripSuffix("$") 18 | 19 | object CirceUtils: 20 | val stringCodec = Codec.from(Decoder[String], Encoder[String]) 21 | 22 | def unwrappedCodec[W, U : Encoder : Decoder](wrap: U => W)(unwrap: W => U): Codec[W] = 23 | Codec.from(Decoder[U], Encoder[U]).iemap(u => Right(wrap(u)))(unwrap) 24 | 25 | def enumCodec[E](valueOf: String => E)(toString: E => String): Codec[E] = 26 | stringCodec.iemap(s => Try(valueOf(s)).toEither.left.map(_.getMessage))(toString) 27 | 28 | def adtCodec[A](typeField: String)(types: AdtEntryCodec[A, ? <: A]*): Codec[A] = new Codec[A]: 29 | override def apply(a: A): Json = 30 | val result = for 31 | entry <- types.find(_.isApplicable(a)) 32 | encoded <- entry.apply(a) 33 | yield encoded.mapObject((typeField -> Json.fromString(entry.getType)) +: _) 34 | 35 | result.getOrElse(throw new RuntimeException(s"Unexpected ADT instance of type ${a.getClass}")) 36 | 37 | override def apply(c: HCursor): Result[A] = for 38 | typeField <- c.get[String](typeField) 39 | adtEntry <- types.find(_.getType == typeField).toRight(DecodingFailure.apply("", c.history)) 40 | instance <- adtEntry.codec.apply(c) 41 | yield instance 42 | -------------------------------------------------------------------------------- /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 | -V hash=true \ 21 | --syntax-definition highlight-scala.xml 22 | -------------------------------------------------------------------------------- /lectures/images/01-intro/boyan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/01-intro/boyan.jpg -------------------------------------------------------------------------------- /lectures/images/01-intro/case-ended.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/01-intro/case-ended.webp -------------------------------------------------------------------------------- /lectures/images/01-intro/cheering-minions.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/01-intro/cheering-minions.gif -------------------------------------------------------------------------------- /lectures/images/01-intro/dany.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/01-intro/dany.jpg -------------------------------------------------------------------------------- /lectures/images/01-intro/grammar-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/01-intro/grammar-size.png -------------------------------------------------------------------------------- /lectures/images/01-intro/odersky.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/01-intro/odersky.JPG -------------------------------------------------------------------------------- /lectures/images/01-intro/pretending-to-write.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/01-intro/pretending-to-write.gif -------------------------------------------------------------------------------- /lectures/images/01-intro/static-type-system.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/01-intro/static-type-system.jpeg -------------------------------------------------------------------------------- /lectures/images/01-intro/vassil.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/01-intro/vassil.jpg -------------------------------------------------------------------------------- /lectures/images/01-intro/viktor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/01-intro/viktor.jpg -------------------------------------------------------------------------------- /lectures/images/01-intro/zdravko.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/01-intro/zdravko.jpg -------------------------------------------------------------------------------- /lectures/images/02-fp-with-scala/functional-wizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/04-key-fp-approaches/filter.png -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/git-objects-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/04-key-fp-approaches/git-objects-1.png -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/git-objects-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/04-key-fp-approaches/git-objects-2.png -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/lego-blocks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/04-key-fp-approaches/list.jpg -------------------------------------------------------------------------------- /lectures/images/04-key-fp-approaches/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/04-key-fp-approaches/vector.jpg -------------------------------------------------------------------------------- /lectures/images/08-concurrency/cpu-cache.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/08-concurrency/cpu-cache.jpeg -------------------------------------------------------------------------------- /lectures/images/08-concurrency/dijkstra.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/08-concurrency/dijkstra.jpg -------------------------------------------------------------------------------- /lectures/images/09-type-classes/category-theory-for-programmers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/09-type-classes/cats-cat.png -------------------------------------------------------------------------------- /lectures/images/09-type-classes/cats-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/09-type-classes/cats-small.png -------------------------------------------------------------------------------- /lectures/images/09-type-classes/cats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/09-type-classes/cats.png -------------------------------------------------------------------------------- /lectures/images/09-type-classes/scala-with-cats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/10-monads-and-applicatives/impure-logo.png -------------------------------------------------------------------------------- /lectures/images/11-cats-and-cats-effects/Screenshot_20220518_173430.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/11-cats-and-cats-effects/Screenshot_20220518_173430.png -------------------------------------------------------------------------------- /lectures/images/11-cats-and-cats-effects/Screenshot_20220518_173528.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/11-cats-and-cats-effects/Screenshot_20220518_173528.png -------------------------------------------------------------------------------- /lectures/images/11-cats-and-cats-effects/cats-effect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/11-cats-and-cats-effects/cats-effect.png -------------------------------------------------------------------------------- /lectures/images/11-cats-and-cats-effects/hierarchy-impure.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/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-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/Left-fold-transformation.png -------------------------------------------------------------------------------- /lectures/images/Right-fold-transformation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/Right-fold-transformation.png -------------------------------------------------------------------------------- /lectures/images/animation-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/animation-reverse.gif -------------------------------------------------------------------------------- /lectures/images/but-why.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/but-why.gif -------------------------------------------------------------------------------- /lectures/images/classhierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/classhierarchy.png -------------------------------------------------------------------------------- /lectures/images/godji-opakovka2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/godji-opakovka2.jpg -------------------------------------------------------------------------------- /lectures/images/reduce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/reduce.png -------------------------------------------------------------------------------- /lectures/images/scala-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/scala-logo.png -------------------------------------------------------------------------------- /lectures/images/scala3-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/scala3-small.png -------------------------------------------------------------------------------- /lectures/images/scala3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/scala3.png -------------------------------------------------------------------------------- /lectures/images/whaaat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/whaaat.webp -------------------------------------------------------------------------------- /lectures/images/work.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/lectures/images/work.png -------------------------------------------------------------------------------- /lectures/images/zipper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-fmi/scala-fmi-2022/25a580437f6f895ecf15c8412b619bebd364f517/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 | --------------------------------------------------------------------------------