├── .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 | [{ height="520" }](https://www.manning.com/books/functional-programming-in-scala)
32 |
33 | # Теория на категориите
34 |
35 | [{ 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 |
--------------------------------------------------------------------------------