├── .gitignore
├── .gitmodules
├── README.md
├── lectures
├── 01-intro.html
├── 01-intro.md
├── 02-scala-intro-code.txt
├── 02-scala-intro.html
├── 02-scala-intro.md
├── 03-oop.html
├── 03-oop.rst
├── 04-functional-programming-basics-code.txt
├── 04-functional-programming-basics.html
├── 04-functional-programming-basics.rst
├── 05-currying-tuples-collections.html
├── 05-currying-tuples-collections.rst
├── 05-currying-tuples-collections.txt
├── 06-pattern-matching-and-adts.txt
├── 07-effects-and-functional-error-handling.txt
├── 08-implicits.html
├── 08-implicits.md
├── 09-LazyEval.txt
├── 09-Streems.txt
├── 10-concurrency.html
├── 10-concurrency.md
├── 11-type-classes.html
├── 11-type-classes.md
├── 12-monads-and-functors.html
├── 12-monads-and-functors.md
├── 14-modularity-and-web.html
├── 14-modularity-and-web.md
├── build.sh
├── examples
│ ├── 01-intro
│ │ ├── HelloWorld.scala
│ │ └── sbt-hello-world
│ │ │ ├── build.sbt
│ │ │ ├── project
│ │ │ └── build.properties
│ │ │ └── src
│ │ │ ├── main
│ │ │ └── scala
│ │ │ │ ├── HelloWorld.scala
│ │ │ │ ├── Lists.scala
│ │ │ │ └── Worksheet.sc
│ │ │ └── test
│ │ │ └── scala
│ │ │ └── ExampleSpec.scala
│ ├── 03-oop
│ │ ├── caseclass.scala
│ │ ├── companion.scala
│ │ ├── oop.scala
│ │ └── traits.scala
│ ├── 09-stream
│ │ └── FStream.scala
│ ├── 10-concurrency
│ │ ├── build.sbt
│ │ ├── project
│ │ │ └── build.properties
│ │ └── src
│ │ │ └── main
│ │ │ └── scala
│ │ │ ├── actors
│ │ │ ├── ActorsExample1.scala
│ │ │ ├── ActorsExample2.scala
│ │ │ ├── ActorsExample3.scala
│ │ │ └── AkkaTypedExample.scala
│ │ │ ├── callbacks
│ │ │ ├── Callbacks.scala
│ │ │ └── FutureApp.scala
│ │ │ ├── concurrent
│ │ │ ├── Executors.scala
│ │ │ ├── FailSafeExample.scala
│ │ │ ├── impl
│ │ │ │ ├── Future.scala
│ │ │ │ └── Promise.scala
│ │ │ └── lecture
│ │ │ │ ├── Future.scala
│ │ │ │ └── Promise.scala
│ │ │ ├── http
│ │ │ ├── FutureWebServer.scala
│ │ │ ├── HttpClient.scala
│ │ │ └── HttpRequests.scala
│ │ │ ├── io
│ │ │ ├── Console.scala
│ │ │ ├── HelloName.scala
│ │ │ └── IO.scala
│ │ │ ├── tasks
│ │ │ └── TaskExample.scala
│ │ │ ├── threads
│ │ │ ├── ThreadsExample.scala
│ │ │ ├── ThreadsSharingData.scala
│ │ │ └── ThreadsSharingData2.scala
│ │ │ └── util
│ │ │ └── Utils.scala
│ ├── 11-type-classes
│ │ ├── build.sbt
│ │ ├── project
│ │ │ └── build.properties
│ │ └── src
│ │ │ └── main
│ │ │ └── scala
│ │ │ ├── cats
│ │ │ ├── CatsMonoidDemo.scala
│ │ │ └── EqDemo.scala
│ │ │ ├── json
│ │ │ ├── Json.scala
│ │ │ ├── JsonDemo.scala
│ │ │ └── Person.scala
│ │ │ ├── lasttime
│ │ │ └── FutureDemo.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
│ ├── 12-monads-and-functors
│ │ ├── lecture-code
│ │ │ ├── build.sbt
│ │ │ ├── project
│ │ │ │ └── build.properties
│ │ │ └── src
│ │ │ │ └── main
│ │ │ │ └── scala
│ │ │ │ ├── concurrent
│ │ │ │ └── FailSafeExample.scala
│ │ │ │ └── effects
│ │ │ │ ├── Functor.scala
│ │ │ │ ├── Monad.scala
│ │ │ │ ├── cats
│ │ │ │ └── FunctorCompositionDemo.scala
│ │ │ │ ├── maybe
│ │ │ │ └── Maybe.scala
│ │ │ │ └── state
│ │ │ │ ├── RNG.scala
│ │ │ │ └── State.scala
│ │ ├── monad-examples
│ │ │ ├── build.sbt
│ │ │ ├── project
│ │ │ │ └── build.properties
│ │ │ └── src
│ │ │ │ └── main
│ │ │ │ └── scala
│ │ │ │ └── example
│ │ │ │ ├── BasicExample.scala
│ │ │ │ ├── EitherExample.scala
│ │ │ │ ├── EitherPractice.scala
│ │ │ │ ├── FutureEitherExample.scala
│ │ │ │ ├── FutureEitherPractice.scala
│ │ │ │ ├── FutureOptionExample.scala
│ │ │ │ ├── FutureOptionPractice.scala
│ │ │ │ ├── Monad.scala
│ │ │ │ ├── OptionExample.scala
│ │ │ │ ├── OptionPractice.scala
│ │ │ │ └── package.scala
│ │ └── state-monad.md
│ └── 14-modularity-and-web
│ │ ├── modularity
│ │ ├── build.sbt
│ │ ├── project
│ │ │ └── build.properties
│ │ └── src
│ │ │ └── main
│ │ │ ├── application.conf
│ │ │ └── scala
│ │ │ └── modularity
│ │ │ ├── Application.scala
│ │ │ ├── a
│ │ │ ├── A1.scala
│ │ │ ├── A2.scala
│ │ │ ├── A3.scala
│ │ │ └── AModule.scala
│ │ │ ├── b
│ │ │ ├── B1.scala
│ │ │ ├── B2.scala
│ │ │ └── BModule.scala
│ │ │ ├── c
│ │ │ ├── C1.scala
│ │ │ ├── C2.scala
│ │ │ └── CModule.scala
│ │ │ └── d
│ │ │ ├── D.scala
│ │ │ └── DModule.scala
│ │ ├── shopping-app
│ │ ├── app
│ │ │ ├── ShoppingApp.scala
│ │ │ ├── authentication
│ │ │ │ ├── AuthenticatedAction.scala
│ │ │ │ └── AuthenticatedRequest.scala
│ │ │ ├── controllers
│ │ │ │ ├── ApplicationController.scala
│ │ │ │ ├── InventoryController.scala
│ │ │ │ ├── ShoppingController.scala
│ │ │ │ └── UserController.scala
│ │ │ ├── inventory
│ │ │ │ ├── Inventory.scala
│ │ │ │ ├── InventoryAdjustment.scala
│ │ │ │ ├── InventoryManager.scala
│ │ │ │ ├── InventoryModule.scala
│ │ │ │ └── Product.scala
│ │ │ ├── shopping
│ │ │ │ ├── Order.scala
│ │ │ │ ├── OrderLine.scala
│ │ │ │ ├── Orders.scala
│ │ │ │ ├── Shop.scala
│ │ │ │ ├── ShoppingCart.scala
│ │ │ │ └── ShoppingModule.scala
│ │ │ └── user
│ │ │ │ ├── RegisteredUsersRepository.scala
│ │ │ │ ├── User.scala
│ │ │ │ ├── UsersModule.scala
│ │ │ │ └── UsersRegistry.scala
│ │ ├── build.sbt
│ │ ├── conf
│ │ │ └── application.conf
│ │ └── project
│ │ │ ├── build.properties
│ │ │ └── plugins.sbt
│ │ ├── web-app
│ │ ├── app
│ │ │ ├── WebApp.scala
│ │ │ └── controllers
│ │ │ │ ├── ApplicationController.scala
│ │ │ │ └── UsersController.scala
│ │ ├── build.sbt
│ │ ├── conf
│ │ │ └── application.conf
│ │ └── project
│ │ │ ├── build.properties
│ │ │ └── plugins.sbt
│ │ └── web-client
│ │ ├── build.sbt
│ │ ├── project
│ │ └── build.properties
│ │ └── src
│ │ └── main
│ │ └── scala
│ │ └── webclient
│ │ ├── UserPoster.scala
│ │ └── WebClientApp.scala
├── generate-presentation.sh
├── images
│ ├── 01-intro
│ │ ├── essential-scala.png
│ │ ├── functional-programming-in-scala.jpg
│ │ ├── functional.jpg
│ │ ├── grammar-size.png
│ │ ├── programming-in-scala.png
│ │ └── types.jpeg
│ ├── 02-scala-intro
│ │ ├── scala_type_hierarchy.svg
│ │ └── scalastic_principles.jpeg
│ ├── 03-oop
│ │ ├── construction.jpg
│ │ ├── enlightenment.jpg
│ │ └── last-time.webp
│ ├── 04-functional-programming-basics
│ │ ├── Purely_functional_tree_after.svg
│ │ ├── filter.png
│ │ ├── function.png
│ │ ├── godji-opakovka2.jpg
│ │ ├── map.png
│ │ └── stack.jpg
│ ├── 05-currying-tuples-collections
│ │ ├── HaskellCurry.jpg
│ │ ├── Left-fold-transformation.png
│ │ ├── Right-fold-transformation.png
│ │ ├── chicken-curry.jpg
│ │ ├── collections-diagram.svg
│ │ ├── reduce.png
│ │ ├── work.png
│ │ └── zipper.png
│ ├── 11-type-classes
│ │ ├── cats-cat.png
│ │ ├── cats-small.png
│ │ ├── cats.png
│ │ └── scala-with-cats.png
│ ├── 12-monads-and-functors
│ │ ├── 47271389-8eea0900-d581-11e8-8e81-5b932e336336.png
│ │ └── functional-programming-in-scala.jpg
│ └── 14-modularity-and-web
│ │ ├── 04fig02_alt.jpg
│ │ └── 04fig03_alt.jpg
└── theme
│ └── theme.css
└── project-instructions.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | .idea
4 | target
5 |
6 | #/lectures/*.html
7 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lectures/reveal-js"]
2 | path = lectures/reveal-js
3 | url = https://github.com/hakimel/reveal.js.git
4 |
--------------------------------------------------------------------------------
/lectures/01-intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Функционално програмиране за напреднали със Scala
3 | ---
4 | # Давайте функционалнооооооо\.....
5 |
6 | 
7 |
8 | # Кратка история на ФП
9 |
10 | ::: incremental
11 |
12 | - Началото
13 | - Развитие на теория
14 | - Практическо навлизане
15 |
16 | :::
17 |
18 | # Scala навсякъде
19 |
20 | ::: incremental
21 |
22 | - Къде се ползва Scala? - Netflix, Amazon, Spotify, Twitter, Airbnb, etc
23 | - Scala с отворен код? - Kafka, Samza, Spark, Akka, Play Framework
24 | - Scala в България! - Ocado, Hopper & others
25 |
26 | :::
27 |
28 | # Кои сме ние?
29 |
30 | # Административни неща
31 |
32 | # Сбирки
33 |
34 | Всяка сряда от 18:00 до 21:00 в зала 101
35 |
36 | # Оценяване
37 |
38 | ::: incremental
39 |
40 | - два теста (около средата и в края на семестъра) -- всеки по 25 точки
41 | - проект -- 50 точки
42 | - (до) 4 задачки през семестъра -- по 5 точки всяка
43 |
44 | :::
45 |
46 | # Оценяване - Скала (no-pun intended)
47 |
48 | ::: { .center-cells }
49 |
50 | +--------+---------+
51 | | Оценка | Точки |
52 | +--------+---------+
53 | | 6 | ≥ 85 |
54 | +--------+---------+
55 | | 5 | 70--85 |
56 | +--------+---------+
57 | | 4 | 55--70 |
58 | +--------+---------+
59 | | 3 | 40-55 |
60 | +--------+---------+
61 | | 2 | \< 40 |
62 | +--------+---------+
63 |
64 | :::
65 |
66 | # SCAlable LAnguage
67 |
68 | - скалира с нуждата на потребителите на езика
69 | - opinionated по подразбиране,
70 | - но лесно позволява алтернативни конструкции/подходи според нуждата
71 |
72 | # Симбиоза на ФП и ООП
73 |
74 | # Детайлна типова система
75 |
76 | { height="520" }
77 |
78 | # Декларативност и композиция
79 |
80 | ```scala
81 | // сумата на 100-те най-добри резултата на пълнолетните състезатели
82 | competitors
83 | .filter(_.age >= 18)
84 | .map(_.score)
85 | .sorted(Ordering[Int].reverse)
86 | .take(100)
87 | .sum
88 | ```
89 |
90 | # Подходящ за DSL-и
91 |
92 | ```scala
93 | def square(x: Int) = x * x
94 | def double(x: Int) = x * 2
95 |
96 | square(double(square(10) * 100)) + 1
97 | ```
98 |
99 | # Подходящ за DSL-и
100 |
101 | ```scala
102 | // "магия"
103 | implicit class Pipe[T](x: T) {
104 | def |>[V](f: T => V) = f(x)
105 | }
106 |
107 | 10 |> square |> { double(_) * 100 } |> square |> { _ + 1 }
108 | ```
109 |
110 | # Нови конструкции
111 |
112 | ```scala
113 | def докато(cond: => Boolean)(body: => Any): Unit = {
114 | if (cond) {
115 | body
116 | докато(cond)(body)
117 | }
118 | }
119 |
120 | var i = 0
121 | докато (i < 10) {
122 | println(i)
123 | i += 1
124 | }
125 | ```
126 |
127 | # Идеоматичен вариант в Scala
128 |
129 | ```scala
130 | (1 until 10).foreach(println)
131 | (1 until 10).sum
132 | ```
133 |
134 | # Нови конструкции
135 |
136 | асинхронни изчисления:
137 |
138 | ```scala
139 | val calculation = Future {
140 | (1 until 10).sum
141 | }
142 | calculation.foreach(result => println("Result: " + result))
143 | println("End of this thread")
144 | ```
145 |
146 | Изход:
147 |
148 | ```scala
149 | End of this thread
150 | Result: 45
151 | ```
152 |
153 | # Силна академична база
154 |
155 | - но създаден за индустрията
156 | - Композиращи се езикови елементи и малка граматика
157 |
158 | # Grammar Size
159 |
160 | { height="520" }
161 |
162 | # Силна академична база
163 |
164 | - но създаден за индустрията
165 | - Композиращи се езикови елементи и малка граматика
166 | - Математическа база за Scala 3
167 |
168 | # Scala 3 и Dotty
169 |
170 | - базиран върху DOT -- математически модел за есенцията на Scala
171 | - Бърз и опростен компилатор, с доста гъвкавост за развитие на езика и неговите инструменти
172 |
173 | # Екосистема и стабилна общност
174 |
175 | # Инсталиране
176 |
177 | - JVM 8 или 11
178 | - [Win/OS X](https://www.oracle.com/technetwork/java/javase/downloads/index.html)
179 | - Ubuntu/Debian: `install openjdk-8-jdk`
180 | - Fedora/Red Hat: `install java-1.8.0-openjdk`
181 | - Scala 2.12
182 | - [Win, Linux](https://www.scala-lang.org/download/)
183 | - OS X: `brew install scala`
184 | - sbt
185 | - [Win](https://www.scala-sbt.org/download.html)
186 | - OS X: `brew install sbt`
187 | - [Linux](https://www.scala-sbt.org/1.x/docs/Installing-sbt-on-Linux.html)
188 |
189 | # Read-eval-print loop (REPL)
190 |
191 | - интерактивен езиков шел
192 | - стартира се от командния ред със `scala`
193 |
194 | # Hello World
195 |
196 | ```scala
197 | object HelloWorld {
198 | def main(args: Array[String]): Unit = {
199 | println("Hello, World!")
200 | }
201 | }
202 | ```
203 |
204 | # Компилиране и изпълнение
205 |
206 | ```
207 | $ scalac HelloWorld.scala
208 | $ scala HelloWorld
209 | Hello, World!
210 | ```
211 |
212 | # sbt, Scala/Simple Build Tool
213 |
214 | build.sbt:
215 |
216 | ```scala
217 | name := "hello-world"
218 | version := "0.1"
219 |
220 | libraryDependencies ++= Seq(
221 | "org.scalatest" %% "scalatest" % "3.0.5" % "test"
222 | )
223 | ```
224 |
225 | # sbt -- Директорийна структура
226 |
227 | - `build.sbt`
228 | - `src/main/scala` -- основен код
229 | - `src/test/scala` - тестове
230 |
231 | # sbt команди
232 |
233 | - sbt \<команда\> -- изпълнява командата
234 | - sbt -- влиза в интерактивен режим
235 | - compile -- компилира кода
236 | - run -- изпълнява обект с `main` метод
237 | - console -- стартира REPL, в който е достъпно всичко от кода
238 |
239 | # IDEs/текстови редактори
240 |
241 | - IntelliJ IDEA ([Community Edition](https://www.jetbrains.com/idea/download)) – ще ползваме основно него
242 | - Scala IDE for Eclipse
243 | - Ensime – IDE възможности за vim, Emacs, Sublime, Atom, VSC
244 |
245 | # Демо с IntelliJ IDEA
246 |
247 | # Тестове
248 |
249 |
250 | ```scala
251 | import org.scalatest._
252 |
253 | class ExampleSpec extends FlatSpec with Matchers {
254 | "+" should "sum two numbers" in {
255 | 2 + 3 should be (5)
256 | }
257 | }
258 | ```
259 |
260 | # Ресурси -- книги
261 |
262 | { height="320" }
263 | { height="320" }
264 | { height="320" }
265 |
266 | # Ресурси
267 |
268 | - [Документация](https://docs.scala-lang.org/)
269 | - [Scala API](https://docs.scala-lang.org/api/all.html)
270 | - [Scala Style Guide](https://docs.scala-lang.org/style/)
271 |
--------------------------------------------------------------------------------
/lectures/02-scala-intro-code.txt:
--------------------------------------------------------------------------------
1 | | \*Въведение в езика Scala
2 | | Vassil Dichev
3 | | 27.02.2019
4 | ---
5 |
6 | | \*Литерали
7 |
8 | ```
9 | 2
10 | --
11 | 1 + 1
12 | --
13 | false
14 | --
15 | 1 == 2
16 | --
17 | "Hey!"
18 | --
19 | 'a'
20 | ```
21 | ---
22 |
23 | | \*Дефиниции
24 |
25 | * променливи- \*var
26 | ```
27 | var i = 0; i = 1 // type inference
28 | ```
29 | --
30 | * immutable- \*val
31 | ```
32 | val a: Int = 1 // идентификатор: тип = стойност
33 | ```
34 | --
35 | ```
36 | val expr = if (a > 42) "голямо" else "малко"
37 | ```
38 | --
39 | * функции- \*def
40 | ```
41 | def add(x: Int, y: Int) = { x + y }
42 | ```
43 | --
44 | * всичко е израз
45 | ```
46 | println("Здравей")
47 | --
48 | val pr = println("Здравей")
49 | ```
50 | --
51 | ```
52 | val assign = i = i + 1
53 | ```
54 | ---
55 |
56 | | \*Незадължителен синтаксис
57 |
58 | ```
59 | 1.to(2)
60 | ```
61 | --
62 | ```
63 | 1 to 2
64 | ```
65 | --
66 | ```
67 | 1 == 2
68 | ```
69 | --
70 | ```
71 | 1.==(2)
72 | ```
73 | ---
74 |
75 | | \*Null, Nothing
76 |
77 | ```
78 | val s: String = null
79 | --
80 | val l: Range = null
81 | --
82 | def ex: Nothing = throw new RuntimeException("boom!")
83 | --
84 | def unimplemented = ???
85 | ```
86 | ---
87 |
88 | | \*Контролни конструкции
89 |
90 | * if
91 | ```
92 | val expr = if (a > 42) "голямо" else "малко"
93 | ```
94 | --
95 | ```
96 | val cond = if (a > 42) "голямо"
97 | ```
98 | --
99 | * while
100 | --
101 | * for
102 | - generators
103 | - definitions
104 | - filters
105 | ```
106 | for (i <- 1 to 4 if i % 2 == 0;
107 | c <- 'a' to 'c';
108 | s = i.toString + c
109 | ) println(s)
110 | ```
111 | ---
112 |
113 | | \*List, Range, String
114 |
115 | ```
116 | List(1, 2, 3)
117 | --
118 | "".isEmpty
119 | --
120 | "Scala".length
121 | --
122 | "Hello".head
123 | --
124 | (1 to 4).tail
125 | --
126 | "Hello".take(4)
127 | --
128 | List(1, 2, 3, 4).drop(2)
129 | ```
130 | ---
131 |
132 | | \*Упражнение
133 |
134 | Генерирайте всички възможни поднизове на даден низ
135 | --
136 | ```
137 | val s = "abcd"
138 |
139 | for (i <- 0 to s.length - 1;
140 | j <- 1 to s.length - i
141 | ) println(i, j)
142 | --
143 |
144 | for (i <- 0 to s.length - 1;
145 | j <- 1 to s.length - i;
146 | sub = s.drop(i).take(j)
147 | ) println(sub)
148 | --
149 |
150 | for { i <- 0 to s.length - 1
151 | j <- 1 to s.length - i
152 | sub = s.drop(i).take(j)
153 | } yield sub
154 | ```
155 | ---
156 |
157 | | \*Упражнение: напишете функция, която проверява за балансирани скоби
158 |
159 | ```
160 | for (c <- s) println(c)
161 | --
162 | def balanced(s: String): Boolean = {
163 | var openParentheses = 0
164 | for (c <- s if openParentheses >=0 ) {
165 | if (c == '(') openParentheses += 1
166 | else if (c == ')') openParentheses -= 1
167 | }
168 | openParentheses == 0
169 | }
170 |
171 | ```
172 | ---
173 | ```
174 | def balanced(s: String) = {
175 | def balanced(s: String, openParentheses: Int): Boolean =
176 | if (s.isEmpty) openParentheses == 0
177 | else if (openParentheses < 0) false
178 | else if (s.head == '(') balanced(s.tail, openParentheses + 1)
179 | else if (s.head == ')') balanced(s.tail, openParentheses - 1)
180 | else balanced(s.tail, openParentheses)
181 |
182 | balanced(s, 0)
183 | }
184 | ```
185 |
--------------------------------------------------------------------------------
/lectures/02-scala-intro.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Въведение в езика Scala
7 |
8 |
9 |
10 |
11 |
17 |
18 |
19 |
20 |
27 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | Въведение в езика Scala
37 |
38 |
39 |
40 | Защо да учим Scala?
41 |
42 | A language that doesn’t affect the way you think about programming is not worth knowing. — Alan Perlis
43 |
44 |
45 |
46 | Принципи на Scala
47 | ![]()
48 |
49 |
50 | Влияния
51 |
52 |
53 | - Обектно-ориентирани (Ruby/Smalltalk/Python)
54 | - Функционални (ML, Haskell)
55 | - Статично типизирани (ML, Haskell)
56 | - JVM (Java)
57 | - Конкурентни (Erlang)
58 |
59 |
60 |
61 |
62 | Основни принципи
63 |
64 |
65 | - Всичко е израз
66 | - Изразите имат стойност и тип
67 | - Типовете се декларират след идентификатора
68 | - REPL (shell)
69 |
70 |
71 |
72 |
73 | Литерали
74 |
75 | - Int
76 | - Boolean
77 | - String
78 | - Char
79 |
80 |
81 |
82 | Дефиниции
83 |
84 |
85 | - Променливи –
var
86 | - променливи –
val
87 | - type inference
88 | - функции –
def
89 |
90 |
91 |
92 |
93 | Незадължителни елементи
94 |
95 |
96 | - ;
97 | - .
98 | - ()
99 |
100 |
101 |
102 |
103 | Йерархия на типовете
104 | ![]()
105 |
106 |
107 | Основни типове
108 |
109 | - Any
110 | - AnyRef
111 | - AnyVal
112 | - Unit
113 | - Null
114 | - Nothing
115 |
116 |
117 |
118 | Контролни структури
119 |
120 |
121 | - if
122 | - while
123 | - for
124 |
125 | - генератори
126 | - дефиниции
127 | - филтри
128 |
129 |
130 |
131 |
132 |
133 | Операции на List, String, Range
134 |
135 |
136 | - isEmpty
137 | - length
138 | - head
139 | - tail
140 | - take
141 | - drop
142 |
143 |
144 |
145 |
146 | Допълнителни ресурсиs
147 |
152 |
153 |
154 | Упражнения
155 |
156 |
157 | - Проверете дали един низ има балансирани скоби
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
187 |
188 |
189 |
--------------------------------------------------------------------------------
/lectures/02-scala-intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Въведение в езика Scala
3 | ---
4 | # Защо да учим Scala?
5 |
6 | > A language that doesn't affect the way you think about programming is
7 | > not worth knowing. --- Alan Perlis
8 |
9 | # Принципи на Scala
10 |
11 | { height="520" }
12 |
13 | # Влияния
14 |
15 | ::: incremental
16 |
17 | - Обектно-ориентирани (Ruby/Smalltalk/Python)
18 | - Функционални (ML, Haskell)
19 | - Статично типизирани (ML, Haskell)
20 | - JVM (Java)
21 | - Конкурентни (Erlang)
22 |
23 | :::
24 |
25 | # Основни принципи
26 |
27 | ::: incremental
28 | - Всичко е израз
29 | - Изразите имат стойност и тип
30 | - Типовете се декларират след идентификатора
31 | - REPL (shell)
32 |
33 | :::
34 |
35 | # Литерали
36 |
37 | - Int
38 | - Boolean
39 | - String
40 | - Char
41 |
42 | # Дефиниции
43 |
44 | ::: incremental
45 |
46 | - Променливи – `var`
47 | - променливи – `val`
48 | - type inference
49 | - функции – `def`
50 |
51 | :::
52 |
53 | # Незадължителни елементи
54 |
55 | ::: incremental
56 |
57 | - ;
58 | - .
59 | - ()
60 |
61 | :::
62 |
63 | # Йерархия на типовете
64 |
65 | 
66 |
67 | # Основни типове
68 |
69 | - Any
70 | - AnyRef
71 | - AnyVal
72 | - Unit
73 | - Null
74 | - Nothing
75 |
76 | # Контролни структури
77 |
78 | ::: incremental
79 |
80 | - if
81 | - while
82 | - for
83 | - генератори
84 | - дефиниции
85 | - филтри
86 |
87 | :::
88 |
89 | # Операции на List, String, Range
90 |
91 | ::: incremental
92 |
93 | - isEmpty
94 | - length
95 | - head
96 | - tail
97 | - take
98 | - drop
99 |
100 | :::
101 |
102 | # Допълнителни ресурсиs
103 |
104 | - [Essential Scala](https://underscore.io/books/essential-scala/)
105 | - [API Docs](http://www.scala-lang.org/api/current/)
106 | -
107 |
108 | # Упражнения
109 |
110 | ::: incremental
111 |
112 | - Проверете дали един низ има балансирани скоби
113 |
114 | :::
115 |
--------------------------------------------------------------------------------
/lectures/03-oop.rst:
--------------------------------------------------------------------------------
1 | Обектно ориентирано програмиране за отчаяни със Скала
2 | ==========================================================
3 | .. class:: center
4 |
5 | |
6 | |
7 | | ФМИ 2018/2019 - Лекция №3
8 | |
9 | |
10 |
11 | Soooo.... last time?
12 | ----------------------------------------
13 |
14 | .. image:: images/03-oop/last-time.webp
15 | :class: scale
16 | :width: 700
17 | :height: 480
18 | :align: center
19 |
20 | Всичка са писали обектно...нали?
21 | ----------------------------------------
22 |
23 | .. class:: incremental
24 |
25 | * Java, C++
26 | * Python/Ruby
27 | * C#, Typescript ?
28 | * Rest of the world
29 |
30 | tl;dr
31 | ----------------------------------------
32 |
33 | .. class:: incremental
34 |
35 | * Ще говорим за типове
36 | * За UAP
37 | * За classes, companion objects, traits and case classes
38 | * Скала интерналс ООП
39 | * ООП + ФП = ???
40 |
41 |
42 | Накратко за nominal vs structured typing
43 | ------------------------------------------
44 |
45 | .. class:: incremental
46 |
47 | * Nominal - Аз съм бозайник, защото са ми татуирали "бозайник" на кожата
48 | * Structural - Аз съм бозайник, защото малкото ми може да суче
49 | * A koe e Скала?
50 |
51 | Основи на ООП
52 | ----------------------------------------
53 |
54 | .. class:: incremental
55 |
56 | * Основен елемент на ООП - class - **но в Scala...**
57 | * Constructors, fields, methods - **но в Scala**
58 | * Access levels - public, protected, private - **но в Scala...**
59 |
60 |
61 | ООП в Скала - Classes
62 | ------------------------------------------
63 |
64 | .. code-block:: scala
65 |
66 | class Animal
67 |
68 | class Mammal(order: String) extends Animal {
69 | var milk: Boolean = true
70 | val theMilk: Boolean = true
71 | def zeMilk: Boolean = true
72 | }
73 |
74 |
75 | ООП в Скала - Companion objects
76 | ------------------------------------------
77 |
78 | .. code-block:: scala
79 |
80 | class Mammal(val order: String) extends Animal {
81 | private def milk: Boolean = true
82 | }
83 |
84 | object Mammal {
85 | private[companion] val RODENT = "rodentia"
86 | private[companion] val PRIMATE = "primates"
87 |
88 | def apply(): Mammal = new Mammal(RODENT)
89 | def apply(order: String): Mammal = new Mammal(order)
90 |
91 | def gotMilk(m:Mammal): Boolean = m.milk
92 | }
93 |
94 | ООП в Скала - Traits
95 | ------------------------------------------
96 |
97 | .. code-block:: scala
98 |
99 | trait Edible {
100 | def isPoisonous: Boolean
101 | def isDelicious: Boolean
102 |
103 | def ICanHazEat(): Boolean = !isPoisonous && isDelicious
104 | }
105 |
106 | class Mammal(
107 | val order: String,
108 | val isDelicious: Boolean = true,
109 | val isPoisonous: Boolean = false,
110 | ) extends Animal with Edible {
111 | private def milk: Boolean = true
112 | }
113 |
114 |
115 | ООП в Скала - Case classes
116 | ------------------------------------------
117 |
118 | .. code-block:: scala
119 |
120 | sealed trait Animal {
121 | val order: String
122 | }
123 |
124 | case class Mammal(
125 | order: String = "rodentia",
126 | milk: Boolean = true,
127 | ) extends Animal
128 |
129 |
130 | Scala ООП в практиката
131 | ------------------------------------------
132 |
133 | .. image:: images/03-oop/construction.jpg
134 | :class: scale
135 | :width: 700
136 | :height: 480
137 | :align: center
138 |
139 |
140 | Next time....
141 | ------------------------------------------
142 |
143 | .. image:: images/03-oop/enlightenment.jpg
144 | :class: scale
145 | :width: 700
146 | :height: 480
147 | :align: center
148 |
149 | References
150 | ------------------------------------------
151 |
152 | * SICP - https://mitpress.mit.edu/sites/default/files/sicp/index.html
153 | * Structural typing examples - https://blog.carbonfive.com/2012/09/23/structural-typing-compile-time-duck-typing/
154 | * UAP - Just google it
155 |
--------------------------------------------------------------------------------
/lectures/04-functional-programming-basics-code.txt:
--------------------------------------------------------------------------------
1 | | \*OOP review
2 | ---
3 | | \* OOP Exercises
4 | How do we emulate Java's protected access if we're in package *pkg*?
5 | --
6 | ```
7 | protected[pkg]
8 | ```
9 |
10 | --
11 | Can we have a *val* and a *def* with the same name?
12 | ```
13 | trait Test {
14 | val test: Boolean
15 | def test: Boolean
16 | }
17 | ```
18 | ---
19 | Can we override a *def* with a *val*?
20 |
21 | ```
22 | trait TestTrait { def test: Boolean }
23 | class TestClass extends TestTrait { val test = true }
24 | ```
25 |
26 | --
27 | What about the reverse?
28 |
29 | ```
30 | trait TestTrait { val test: Boolean }
31 | class TestClass extends TestTrait { def test = true }
32 | ```
33 | ---
34 | | \*Default parameters
35 | ```
36 | def balanced(s: String) = {
37 | def balanced(s: String, openParentheses: Int): Boolean =
38 | if (s.isEmpty) openParentheses == 0
39 | else if (openParentheses < 0) false
40 | else if (s.head == '(') balanced(s.tail, openParentheses + 1)
41 | else if (s.head == ')') balanced(s.tail, openParentheses - 1)
42 | else balanced(s.tail, openParentheses)
43 |
44 | balanced(s, 0)
45 | }
46 | ```
47 | ---
48 | | \*Default parameters
49 | ```
50 | def balanced(s: String, openParentheses: Int = 0): Boolean =
51 | if (s.isEmpty) openParentheses == 0
52 | else if (openParentheses < 0) false
53 | else if (s.head == '(') balanced(s.tail, openParentheses + 1)
54 | else if (s.head == ')') balanced(s.tail, openParentheses - 1)
55 | else balanced(s.tail, openParentheses)
56 | ```
57 | ---
58 | What do you think happens when a method with
59 | default parameters conflicts an overloaded method?
60 |
61 | ```
62 | def test(a: Int, b: Int = 0) = a + b
63 | def test(x: Int, y: Int) = x * y
64 |
65 | --
66 | def test(a: Int, b: Int = 0) = a + b
67 | def test(x: Int) = x * 2
68 | ```
69 | ---
70 | | \*Substitution model
71 |
72 | ```
73 | (1+2) * (1+2)
74 | --
75 | 3 * 3
76 | --
77 | 9
78 | ```
79 | ---
80 | ```
81 | def factorial(n: Int): Int = {
82 | if (n == 0) 1
83 | else n * factorial(n - 1)
84 | }
85 |
86 | factorial(5)
87 | --
88 | ```
89 | ---
90 | ```
91 | fact(5)
92 | --
93 | 5 * fact(5 - 1) =
94 | --
95 | 5 * fact(4) =
96 | --
97 | 5 * (4 * fact(4 - 1)) =
98 | --
99 | 5 * (4 * fact(3)) =
100 | --
101 | 5 * (4 * (3 * fact(3 - 1))) =
102 | --
103 | 5 * (4 * (3 * fact(2))) =
104 | --
105 | 5 * (4 * (3 * (2 * fact(2 - 1)))) =
106 | --
107 | 5 * (4 * (3 * (2 * fact(1)))) =
108 | --
109 | 5 * (4 * (3 * (2 * 1)))
110 | ```
111 | ---
112 | ```
113 | import annotation.tailrec
114 | @tailrec def factorialTail(n: Int, acc: Int = 1): Int = {
115 | if (n == 0) acc
116 | else factorialTail(n - 1, n * acc)
117 | }
118 |
119 | factorialTail(5)
120 | ```
121 | ---
122 | ```
123 | @tailrec
124 | def size(l: List[Int], acc: Int = 0): Int =
125 | --
126 | if (l.isEmpty) acc
127 | else size(l.tail, 1 + acc)
128 | ```
129 | ---
130 | ```
131 | @tailrec
132 | def size(l: List[Int], acc: Int = 0): Int =
133 | if (l.isEmpty) acc
134 | else size(l.tail, 1 + acc)
135 | ```
136 | ---
137 | ```
138 | def sum(a: Int, b: Int) = a + b
139 | --
140 | val sumFun = sum
141 | --
142 | val sumFun = sum _
143 | --
144 | val sumFun: (Int, Int) => Int = sum
145 | --
146 | val sumFun2: Function2[Int,Int,Int] = sum
147 | ```
148 | ---
149 | | \*Composing functions
150 |
151 | ```
152 | def len(s: String) = s.length
153 | --
154 | def even(i: Int) = i % 2 == 0
155 | --
156 | (even _).compose(len _)
157 | --
158 | len _ andThen even
159 | ```
160 | ---
161 | ```
162 | @tailrec
163 | def map(l: List[Int], f: Int => Int, acc: List[Int] = Nil): List[Int] =
164 | --
165 | if (l.isEmpty) acc.reverse
166 | else map(l.tail, f, f(l.head) :: acc)
167 | ```
168 | ---
169 | ```
170 | val f = new Function2[Int,Int,Int] {
171 | def apply(x: Int, y: Int): Int = x + y
172 | }
173 | --
174 | val f: (Int, Int) => Int = (x, y) => x + y
175 | --
176 | val f: (Int, Int) => Int = _ + _
177 | --
178 | val add2 = sum(2, _: Int)
179 | --
180 | List(1, 2, 3) map add2
181 | --
182 | List(1, 2, 3) map (sum(2, _))
183 | ```
184 | ---
185 | ```
186 | val list = List(1, 2, 3)
187 |
188 | for (x <- list if x % 2 != 0) yield x + 1
189 |
190 | list.filter(x => x % 2 != 0).map(_ + 1)
191 | ```
192 |
--------------------------------------------------------------------------------
/lectures/04-functional-programming-basics.rst:
--------------------------------------------------------------------------------
1 | Scala- functional programming
2 | =============================
3 |
4 |
5 | :author: Vassil Dichev
6 | :date: 13.03.2019
7 |
8 | From OOP to FP
9 | --------------
10 | Object-oriented programming makes code understandable by encapsulating moving parts. Functional programming makes code understandable by minimizing moving parts.
11 |
12 | -- Michael Feathers
13 |
14 |
15 | Functions
16 | ---------
17 |
18 | .. sidebar:: \
19 |
20 | .. image:: images/04-functional-programming-basics/function.png
21 | :class: scale
22 | :width: 375
23 | :height: 375
24 | :align: center
25 |
26 | * Math: mapping inputs to outputs
27 |
28 | * Programming: decompose and reuse
29 |
30 | Functional programming
31 | ----------------------
32 |
33 | * Referential transparency
34 |
35 | * Functions as first-class
36 |
37 | Referential transparency
38 | ------------------------
39 |
40 | .. code-block:: scala
41 |
42 | val a =
43 | List(a, a)
44 |
45 | List(, )
46 |
47 | Referential transparency
48 | ------------------------
49 |
50 | .. code-block:: scala
51 |
52 | val a = 1 + 2
53 | List(a, a)
54 |
55 | List(1 + 2, 1 + 2)
56 |
57 | Referential transparency
58 | ------------------------
59 |
60 | .. code-block:: scala
61 |
62 | val a = println("TMTOWTDI")
63 | List(a, a)
64 |
65 | List(println("TMTOWTDI"), println("TMTOWTDI"))
66 |
67 | Referential transparency
68 | ------------------------
69 |
70 | .. code-block:: scala
71 |
72 | val a = i += 1
73 | List(a, a)
74 |
75 | List(i += 1, i += 1)
76 |
77 | Referential transparency
78 | ------------------------
79 |
80 | .. code-block:: scala
81 |
82 | val a = ???
83 | List(a, a)
84 |
85 | List(???, ???)
86 |
87 | Referential transparency
88 | ------------------------
89 |
90 | .. code-block:: scala
91 |
92 | val a = new MyObject
93 | List(a, a)
94 |
95 | List(new MyObject, new MyObject)
96 |
97 | Pure functional programming
98 | ---------------------------
99 |
100 | * Total: an output for every input.
101 |
102 | * Deterministic: Same output for the same input.
103 |
104 | * Pure: only effect is computing the output.
105 |
106 | Life without mutation
107 | ---------------------
108 |
109 | * Recursion
110 |
111 | * Persistent data structures
112 |
113 | * Local mutation
114 |
115 | Recursion
116 | ---------
117 |
118 | .. image:: images/04-functional-programming-basics/stack.jpg
119 | :class: scale
120 | :width: 687
121 | :height: 526
122 | :align: center
123 |
124 | Tail recursion
125 | --------------
126 |
127 | * @tailrec
128 |
129 | * accumulators
130 |
131 | Immutability
132 | ------------
133 |
134 | Classes should be immutable unless there's a very good reason to make them mutable
135 |
136 | -- Joshua Bloch, Effective Java
137 |
138 | Immutable objects are simple. Immutable objects are also safer. Immutable objects are always threads-safe.
139 |
140 | -- Brian Goetz, Java Concurrency in Practice
141 |
142 | Persistent data structures
143 | --------------------------
144 |
145 | .. image:: images/04-functional-programming-basics/Purely_functional_tree_after.svg
146 | :class: scale
147 | :width: 437
148 | :height: 415
149 | :align: center
150 |
151 | Implement a tail-recursive function
152 | -----------------------------------
153 |
154 | * size: takes a list, returns size
155 |
156 | * concat: takes 2 lists, returns concatenated list
157 |
158 | * reverse: takes list, returns reversed list
159 |
160 | Functions
161 | ---------
162 |
163 | * Local functions
164 |
165 | * Function literals
166 |
167 | Higher-order functions
168 | ----------------------
169 |
170 | Combinators are arguably the most reusable constructs we have in programming
171 |
172 | -- Dean Wampler
173 |
174 | * filter
175 |
176 | * map
177 |
178 | * foldLeft
179 |
180 | filter
181 | ------
182 |
183 | .. image:: images/04-functional-programming-basics/filter.png
184 | :class: scale
185 | :width: 640
186 | :height: 310
187 |
188 | map
189 | ---
190 |
191 | .. image:: images/04-functional-programming-basics/map.png
192 | :class: scale
193 | :width: 640
194 | :height: 305
195 |
196 | Syntax sugar
197 | ------------
198 |
199 | .. image:: images/04-functional-programming-basics/godji-opakovka2.jpg
200 | :height: 520
201 |
202 | Additional resources
203 | --------------------
204 |
205 | * `Scastie `_
206 |
207 | * `Scala Fiddle `_
208 |
209 | * `API Docs `_
210 |
211 | * `Cube composer `_
212 |
213 | .. |date| date:: %d.%m.%Y
214 |
--------------------------------------------------------------------------------
/lectures/05-currying-tuples-collections.rst:
--------------------------------------------------------------------------------
1 | Scala- FP
2 | =========
3 |
4 |
5 | :author: Vassil Dichev
6 |
7 | Curry
8 | -----
9 |
10 | .. image:: images/05-currying-tuples-collections/chicken-curry.jpg
11 | :class: scale
12 | :width: 640
13 | :height: 480
14 | :align: center
15 |
16 | Haskell Curry
17 | -------------
18 |
19 | .. image:: images/05-currying-tuples-collections/HaskellCurry.jpg
20 | :class: scale
21 | :width: 279
22 | :height: 343
23 | :align: center
24 |
25 | Joke time
26 | ---------
27 |
28 | A team of functional programmers were wondering what to order for lunch.
29 |
30 | In the end they decided to order curry- it leads to fewer arguments.
31 |
32 | What can these functions do?
33 | ----------------------------
34 |
35 | .. code-block:: scala
36 |
37 | def f1[A](l: List[A]): Int
38 |
39 | def f2[A](l: List[A]): List[A]
40 |
41 | reduce
42 | ------
43 |
44 | .. image:: images/05-currying-tuples-collections/reduce.png
45 | :class: scale
46 | :width: 640
47 | :height: 320
48 |
49 | Folding right or left
50 | ---------------------
51 |
52 | .. image:: images/05-currying-tuples-collections/Right-fold-transformation.png
53 | :class: scale
54 | :width: 320
55 | :height: 158
56 | :align: center
57 |
58 | .. image:: images/05-currying-tuples-collections/Left-fold-transformation.png
59 | :class: scale
60 | :width: 320
61 | :height: 158
62 | :align: center
63 |
64 | Lazy
65 | ----
66 |
67 | .. image:: images/05-currying-tuples-collections/work.png
68 | :class: scale
69 | :width: 320
70 | :height: 262
71 | :align: center
72 |
73 | Implement in terms of foldLeft
74 | ------------------------------
75 |
76 | .. class:: incremental
77 |
78 | * size
79 |
80 | * max
81 |
82 | * contains
83 |
84 | * reverse
85 |
86 | Tuple2
87 | ------
88 |
89 | .. class:: incremental
90 |
91 | * partition
92 |
93 | * span
94 |
95 | * splitAt
96 |
97 | Collections
98 | -----------
99 |
100 | .. image:: images/05-currying-tuples-collections/collections-diagram.svg
101 | :class: scale
102 | :width: 451
103 | :height: 415
104 | :align: center
105 |
106 | Imports
107 | -------
108 |
109 | * on-demand
110 |
111 | * selector
112 |
113 | * renaming
114 |
115 | Functions
116 | ---------
117 |
118 | .. class:: incremental
119 |
120 | * flatten
121 |
122 | * flatMap
123 |
124 | * groupBy
125 |
126 | * zip
127 |
128 | .. |date| date:: %d.%m.%Y
129 |
130 | Zip
131 | ---
132 |
133 | .. image:: images/05-currying-tuples-collections/zipper.png
134 | :class: scale
135 | :width: 290
136 | :height: 400
137 | :align: center
138 |
--------------------------------------------------------------------------------
/lectures/05-currying-tuples-collections.txt:
--------------------------------------------------------------------------------
1 | | \*Currying and multiple parameter lists
2 |
3 | ```
4 | def sum(x: Int, y: Int) = x + y
5 | --
6 | def sumCurried(x: Int)(y: Int) = x + y
7 | --
8 | val sum2 = sumCurried(2) _
9 | sum2(3)
10 | --
11 | val sumFunc = sum _
12 | val sumCurr = sumFunc.curried
13 | ```
14 | ---
15 | | \*takeWhile
16 |
17 | ```
18 | import annotation.tailrec
19 | @tailrec
20 | def takeWhile[A](
21 | l: List[A], acc: List[A] = Nil
22 | )(
23 | f: A => Boolean
24 | ): List[A] =
25 | --
26 | if (l.isEmpty || !f(l.head)) acc.reverse
27 | else takeWhile(l.tail, l.head :: acc)(f)
28 | ```
29 | ---
30 | | \*takeWhile
31 |
32 | ```
33 | import annotation.tailrec
34 | @tailrec
35 | def takeWhile[A](
36 | l: List[A], acc: List[A] = Nil
37 | )(
38 | f: A => Boolean
39 | ): List[A] =
40 | if (l.isEmpty || !f(l.head)) acc.reverse
41 | else takeWhile(l.tail, l.head :: acc)(f)
42 | ```
43 | ---
44 | ```
45 | def map[A,B](l: List[A])(f: A => B): List[B] =
46 | --
47 | l.foldRight(List.empty[B]) {
48 | (x, l) => f(x) :: l
49 | }
50 | ```
51 | ---
52 | ```
53 | def map[A,B](l: List[A])(f: A => B): List[B] =
54 | l.foldRight(List.empty[B]) {
55 | (x, l) => f(x) :: l
56 | }
57 | ```
58 | ---
59 | ```
60 | def filter[A](l: List[A])(f: A => Boolean): List[A] =
61 | --
62 | l.foldRight(List.empty[A]) {
63 | (x, l) => if (f(x)) x :: l else l
64 | }
65 | ```
66 | ---
67 | ```
68 | def filter[A](l: List[A])(f: A => Boolean): List[A] =
69 | l.foldRight(List.empty[A]) {
70 | (x, l) => if (f(x)) x :: l else l
71 | }
72 | ```
73 | ---
74 | | \* Fold and laziness
75 |
76 | ```
77 | def forall[A](l: List[A])(f: A => Boolean): Boolean =
78 | ```
79 | ---
80 | | \* Fold and laziness
81 |
82 | ```
83 | def forall[A](l: List[A])(f: A => Boolean): Boolean =
84 | l.foldLeft(true) { (acc, x) => f(x) && acc }
85 |
86 | --
87 | def forall[A](l: List[A])(f: A => Boolean): Boolean = {
88 | l.foldLeft(true) { (acc, x) =>
89 | println("Executing for " + x)
90 | f(x) && acc
91 | }
92 | }
93 | ```
94 | ---
95 | | \* forall/exists
96 |
97 | ```
98 | def exists[A](l: List[A])(f: A => Boolean): Boolean =
99 | --
100 | !forall(l)(!f(_))
101 | ```
102 | ---
103 | | \* forall/exists
104 |
105 | ```
106 | def exists[A](l: List[A])(f: A => Boolean): Boolean =
107 | !forall(l)(!f(_))
108 | ```
109 | ---
110 | | \*Tuples
111 |
112 | ```
113 | val t1: (Int, String) = (1, "hi")
114 | --
115 | val t2: Tuple2[Int,String] = Tuple2(1, "hi")
116 | --
117 | val t3 = 1 -> "hi"
118 | --
119 | val a = t1._1
120 | val b = t3._2
121 | ```
122 | ---
123 | | \*Imports
124 |
125 | ```
126 | import collection.mutable._
127 | --
128 | import collection.mutable.{Set,Map}
129 | --
130 | import collection.mutable.{List => MList}
131 | --
132 | import collection.mutable.{_, List => _}
133 | ```
134 | ---
135 | | \*Collections are functions
136 |
137 | ```
138 | val dict = Map(1 -> "one", 2 -> "two")
139 | List(1, 2) map dict
140 |
141 | --
142 | val arr = Array(5, 3, 4)
143 | List(2, 1) map arr
144 |
145 | --
146 | val set = Set(1, 3, 5, 8)
147 | (1 to 10) map set
148 | ```
149 | ---
150 | | \*Uniform return type principle
151 |
152 | ```
153 | arr map (_ + 2)
154 | --
155 | dict map (t => t._1.toString -> t._2)
156 | --
157 | import Function.tupled
158 | dict map tupled(_.toString -> _)
159 | --
160 | dict.mapValues("<" + _ + ">")
161 | --
162 | set map (_ / 2)
163 | ```
164 | ---
165 | | \*Partial functions
166 | --
167 | ```
168 | val reciprocal = new PartialFunction[Int,Double] {
169 | override def isDefinedAt(x: Int) = x != 0
170 | override def apply(x: Int) =
171 | if (isDefinedAt(x)) 1.toDouble / x
172 | else throw new MatchError("Division by zero")
173 | }
174 | ```
175 | ---
176 | | \*flatten/flatMap
177 |
178 | Generate all combinations of numbers
179 |
180 | ```
181 | val s = "abcd"
182 | val prefixes = s.inits.toList
183 | val suffixes = s.tails.toList
184 | --
185 | val lol = prefixes.map(_.tails.toList)
186 | --
187 | lol.flatten
188 | --
189 | prefixes.flatMap(_.tails)
190 | --
191 | lol.flatMap(identity) == lol.flatten
192 | --
193 | for {
194 | x <- l
195 | y <- l
196 | } yield (x, y)
197 | ```
198 | ---
199 | | \*Merging maps
200 |
201 | ```
202 | val eng = Map(1 -> "one", 2 -> "two")
203 | val ger = Map(1 -> "eins", 3 -> "drei")
204 |
205 | --
206 | List(eng, ger).flatten.groupBy(_._1).mapValues(_.map(_._2))
207 | ```
208 | ---
209 | | \*zip
210 |
211 | Exercise: Find the differences between consecutive numbers
212 |
213 | ```
214 | val l = List(1, 3, 8, 11, 15, 17, 24, 27, 32)
215 |
216 | --
217 | (l zip l.tail)
218 |
219 | --
220 | (l.tail zip l) map tupled(_ - _)
221 | ```
222 |
--------------------------------------------------------------------------------
/lectures/07-effects-and-functional-error-handling.txt:
--------------------------------------------------------------------------------
1 | | \*Functions and ADTs
2 |
3 | ```
4 | def f1(b: Boolean): Boolean
5 | ```
6 | --
7 | Complexity: 4
8 | --
9 |
10 | ```
11 | def f2(b: Option[Boolean]): Boolean
12 | ```
13 | --
14 | Complexity: 8
15 |
16 | --
17 | Functions have exponential complexity
18 | --
19 |
20 | ```
21 | def f3(b: Byte): Boolean
22 | def f4(b: Boolean): Byte
23 | ```
24 |
25 | ---
26 | | \*Why exponential?
27 |
28 | f2 as a mapping between input and output values.
29 |
30 | Input │ Option │ Some(true) │ Some(false)
31 | ───────┼────────┼────────────┼────────────
32 | Output │ false │ false │ false
33 | Output │ false │ false │ true
34 | Output │ false │ true │ false
35 | Output │ false │ true │ true
36 | Output │ true │ false │ false
37 | Output │ true │ false │ true
38 | Output │ true │ true │ false
39 | Output │ true │ true │ true
40 | ---
41 | | \*Smart constructors
42 | ```
43 | case class Employee(name: String, age: Int)
44 | --
45 | object Employee {
46 | def apply(name: String, age: Int): Option[Employee] =
47 | if (age >= 18) Some(new Employee(name, age)) else None
48 | }
49 | --
50 | sealed abstract case class Employee(name: String, age: Int)
51 | object Employee {
52 | def apply(name: String, age: Int): Option[Employee] =
53 | if (age >= 18) Some(new Employee(name, age){}) else None
54 | }
55 | ```
56 |
57 | ---
58 |
59 | | \*Scala- Functional Effects
60 |
61 | | Vassil Dichev
62 |
63 | | 03.04.2019
64 | ---
65 |
66 | | \*Effects
67 |
68 | * Partiality
69 | * Exceptions/errors
70 | * Nondeterminism
71 | * Dependency injection/configuration
72 | * Logging
73 | * Mutable state
74 | * Input/output
75 | * Asynchronicity
76 |
77 | ---
78 | | \*Effects
79 |
80 | * Partiality- Option
81 | --
82 | * Exceptions/errors- Try/Either
83 | --
84 | * Nondeterminism- List
85 | --
86 | * Dependency injection- Reader
87 | * Logging- Writer
88 | * Mutable state- State
89 | --
90 | * Input/Output- IO
91 | --
92 | * Asynchronicity- Future
93 |
94 | ---
95 | | \* Type aliases
96 |
97 | ```
98 | type Params = Map[String,String]
99 | ```
100 | ---
101 | | \*Partiality and exceptions
102 |
103 | ```
104 | def extract(params: Params) = params("num")
105 | def parse(s: String) = s.toInt
106 | val reciprocal: PartialFunction[Int,Double] = {
107 | case x if x != 0 => 1.toDouble / x
108 | }
109 | val process = extract _ andThen parse andThen reciprocal
110 | ```
111 | ---
112 | | \* Why even try?
113 |
114 | ```
115 | try {
116 | try {
117 | } finally {
118 | }
119 | try {
120 | try {
121 | } finally {
122 | }
123 | } finally {
124 | }
125 | } finally {
126 | try {
127 | } finally {
128 | }
129 | }
130 |
131 | ```
132 | ---
133 | | \*Exceptional problems
134 |
135 | * No static guarantees
136 | --
137 | * Hard to compose
138 | --
139 | * Coupled error handling
140 | --
141 | * Tied to the current thread
142 | --
143 | * Not a value
144 | ---
145 | | \*Option
146 |
147 | ```
148 | def extractMaybe(params: Params) = params.get("num")
149 | def parseMaybe(s: String) = try {
150 | Some(s.toInt)
151 | } catch {
152 | case e: NumberFormatException => None
153 | }
154 | val reciprocalMaybe = reciprocal.lift
155 | --
156 | def processMap(params: Params) =
157 | (extractMaybe(params) map parseMaybe)
158 |
159 | ```
160 | ---
161 | | \*Combining options
162 |
163 | ```
164 | def processMaybe(params: Params) =
165 | extractMaybe(params)
166 | .flatMap(parseMaybe)
167 | .flatMap(reciprocalMaybe)
168 | --
169 | def processMaybe(params: Params) = for {
170 | param <- extractMaybe(params)
171 | num <- parseMaybe(param)
172 | r <- reciprocalMaybe(num)
173 | } yield r
174 | ```
175 | ---
176 | | \*Try
177 |
178 | ```
179 | import util.{Try,Success,Failure}
180 | val s = Success(1)
181 | val f = Failure(new RuntimeException("Something went wrong"))
182 | --
183 | def extractTry(params: Params) = Try(params("num"))
184 | def parseTry(s: String) = Try(s.toInt)
185 | def reciprocalTry(i: Int) = Try(reciprocal(i))
186 | ```
187 | ---
188 | | \*Failure is not an Option
189 |
190 | ```
191 | def processTry(params: Params) = for {
192 | param <- extractTry(params)
193 | num <- parseTry(param)
194 | r <- reciprocalTry(num)
195 | } yield r
196 |
197 | ```
198 | ---
199 | | \* Either
200 |
201 | ```
202 | val right: Either[String,Int] = Right(1)
203 | val left : Either[String,Int] = Left("Something went wrong")
204 | --
205 | val o1: Option[Int] = Some(1)
206 | val o2: Option[Int] = None
207 | o1.toRight("Error message")
208 | --
209 | o2.toRight("Error message")
210 | ```
211 |
212 | ---
213 | | \*Modeling errors
214 |
215 | ```
216 | sealed trait ProcessingError
217 | case class KeyNotFound(key: String)
218 | case class NotNumeric(s: String)
219 | case object DivisionByZero
220 | --
221 | def processEither(params: Params) = for {
222 | param <- extractMaybe(params) toRight KeyNotFound("num")
223 | num <- parseMaybe(param) toRight NotNumeric(param)
224 | r <- reciprocalMaybe(num) toRight DivisionByZero
225 | } yield r
226 | ```
227 | ---
228 | | \* IO
229 |
230 | ```
231 | case class IO[A](val unsafeRun: () => A)
232 | object Console {
233 | def putStrLn(line: String): IO[Unit] = IO(() => println(line))
234 | def getStrLn: IO[String] = IO(() => readLine())
235 | }
236 | import Console._
237 | --
238 | val run = putStrLn("Hello")
239 | (run, run)
240 | --
241 | (putStrLn("Hello"), putStrLn("Hello"))
242 | ```
243 |
244 | ---
245 | | \* IO with map
246 |
247 | ```
248 | case class IO[A](val unsafeRun: () => A) {
249 | def map[B](f: A => B) = IO(() => f(this.unsafeRun()))
250 | }
251 | object Console {
252 | def putStrLn(line: String): IO[Unit] = IO(() => println(line))
253 | def getStrLn: IO[String] = IO(() => readLine())
254 | }
255 | import Console._
256 | ```
257 | ---
258 | | \* IO with flatMap
259 |
260 | ```
261 | case class IO[A](val unsafeRun: () => A) {
262 | def map[B](f: A => B) = IO(() => f(this.unsafeRun()))
263 | def flatMap[B](f: A => IO[B]): IO[B] =
264 | IO(() => f(this.unsafeRun()).unsafeRun())
265 | }
266 | object Console {
267 | def putStrLn(line: String): IO[Unit] = IO(() => println(line))
268 | def getStrLn: IO[String] = IO(() => readLine())
269 | }
270 |
271 | ```
272 | ---
273 | | \* IO in for comprehensions
274 |
275 | ```
276 | import Console._
277 | val program = for {
278 | _ <- putStrLn("What is your name?")
279 | name <- getStrLn
280 | _ <- putStrLn("Hello, " + name + ", welcome!")
281 | } yield ()
282 | ```
283 |
284 | ---
285 | | \*Advantages of IO
286 |
287 | * It's a value- can combine, optimize, etc.
288 | --
289 | * Asynchronicity
290 | --
291 | * Cancellation
292 | --
293 | * Scheduling and retrying
294 | --
295 | * Test and production instances
296 | --
297 | * Different frontends
298 | ---
299 | | \* Covariance
300 |
301 | B List[B]
302 | \b↑\s \r⇒\s \b↑\s
303 | A List[A]
304 | ---
305 | | \*Covariance vs Invariance
306 |
307 | ```
308 | abstract class List[+T]
309 | --
310 | class A; class B extends A
311 | --
312 | val l = List(new B)
313 | val l2: List[A] = l
314 | --
315 | val a = Array(new B)
316 | val a2: Array[A] = a
317 | ```
318 | ---
319 | | \*Contravariance
320 |
321 | ```
322 | trait Function1[-T1, +R]
323 | --
324 | val f: B => A = (a: A) => new B
325 | --
326 | val f: A => B = (a: B) => new A
327 | ```
328 | ---
329 | | \* Covariant type bounds
330 |
331 | ```
332 | import collection.mutable.ArrayOps
333 | def refArrayOps[T <: AnyRef](xs: Array[T]): ArrayOps[T] =
334 | new ArrayOps.ofRef[T](xs)
335 | ```
336 | ---
337 | | \* Contravariant type bounds
338 |
339 | ```
340 | case class Box[+A](value: A) {
341 | def update(a: A): Box[A] = Box(a)
342 | }
343 | --
344 | case class Box[+A](value: A) {
345 | def update[AA >: A](a: AA): Box[AA] = Box(a)
346 | }
347 | ```
348 |
--------------------------------------------------------------------------------
/lectures/08-implicits.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Implicits in Scala
3 | ---
4 | #
5 |
6 | > If there is one feature that makes Scala "Scala", I would pick implicits.
7 | -- Martin Odersky
8 |
9 | # Implicits in Scala
10 |
11 | - Language feature
12 | - Allow omitting method calls or variable references
13 | - Compilation safety
14 |
15 | # Implicits in Scala
16 |
17 | - Values labeled with an `implicit` modifier can be passed to implicit parameters and used as implicit conversions
18 | - `implicit` is an illegal modifier for top-level objects
19 |
20 | # The compiler does not try to apply implicits if the code typechecks!
21 |
22 | # Implicit conversions
23 |
24 | - Historically came first in the language
25 | - Allow arbitrary classes to implement new interfaces
26 | - Prefer avoiding implicit conversions unless you have a very good reason to use them
27 | - Scala 3 will restrict usage of implicit conversions
28 |
29 | # Implicit conversions
30 |
31 | An implicit conversion from type `A` to type `B` is defined by an implicit value that has a type signature `S => T` or `(=> S) => T`
32 |
33 | # Implicit conversions
34 |
35 | ```scala
36 | scala> val number: Double = 1
37 | number: Double = 1.0
38 | ```
39 |
40 | - scala.Predef
41 |
42 | # Implicit conversions
43 |
44 | ```scala
45 | scala> val number: Int = 1.23
46 | :11: error: type mismatch;
47 | found : Double(1.23)
48 | required: Int
49 | val number: Int = 1.23
50 | ^
51 | ```
52 |
53 | # Implicit conversions
54 |
55 | ```scala
56 | scala> implicit def doubleToInt(x: Double): Int = x.toInt
57 | doubleToInt: (x: Double)Int
58 | ```
59 |
60 | # Implicit conversions
61 |
62 | ```scala
63 | scala> val number: Int = 1.23
64 | number: Int = 1
65 | ```
66 |
67 | # Implicit conversions
68 |
69 | ```scala
70 | scala> val number: Int = doubleToInt(1.23)
71 | number: Int = 1
72 | ```
73 |
74 | # Rules
75 |
76 | # Marking
77 |
78 | - Definitions must be explicitly marked implicit to become available
79 | - Variables, functions and object definitions can be marked `implicit`
80 |
81 | ```scala
82 | def doubleToInt(double: Double): Int = double.toInt
83 |
84 | implicit def doubleToInt(double: Double): Int = double.toInt
85 | ```
86 |
87 | # Scope
88 |
89 | - An implicit conversion must be in scope as a single identifier
90 | - Otherwise the compiler will not consider it
91 | - Companion objects bring member implicits in scope
92 |
93 | # Resolution order
94 |
95 | - Current scope
96 | - Imports
97 | - Companion objects
98 | - Parameter arguments
99 | - Type parameters
100 | - Outer objects of nested types
101 | - Parent objects
102 |
103 | # One at a time
104 |
105 | - The compiler only considers a single implicit insertion at a time
106 | - This is done for compilation performance considerations
107 | - It is possible to circumvent this
108 |
109 | # Resolving ambiguity
110 |
111 | - If there are more than one matching implicits, the most specific one is chosen
112 | - If there is no unique most specific candidate, a compile error is reported
113 |
114 |
115 | # When does the compiler try to apply implicits?
116 |
117 | :::incremental
118 |
119 | - Implicit conversion to an expected type
120 | - Implicit conversion of a method call receiver
121 | - Implicit parameters
122 |
123 | :::
124 |
125 | # Implicit conversion to an expected type
126 |
127 |
128 |
129 | ```scala
130 | // ISO 3166-1 alpha-2
131 | class CountryCode(val code: String)
132 |
133 | implicit def stringToCountryCode(str: String): CountryCode = new CountryCode(str)
134 |
135 | def currencyFor(country: CountryCode): Currency = ???
136 |
137 | currencyFor(country = "BG")
138 |
139 | // Expanded
140 | currencyFor(country = CountryCode("BG"))
141 | ```
142 |
143 |
144 |
145 | # Implicit conversion of a method call receiver
146 |
147 | # Type interop
148 |
149 | ```scala
150 | class Rational(val n: Int, val d: Int) {
151 | def +(other: Rational): Rational = ???
152 | def +(other: Int): Rational = ???
153 | }
154 |
155 | new Rational(1, 2) + new Rational(3, 4)
156 |
157 | new Rational(1, 3) + 2
158 |
159 | 1 + new Rational(1, 2) // Type mismatch
160 | ```
161 |
162 | # Type interop
163 |
164 | ```scala
165 | implicit def intToRational(x: Int): Rational = new Rational(x, 1)
166 |
167 | 1 + new Rational(1, 2) // Compiles correctly
168 | ```
169 |
170 | # Simulate syntax
171 |
172 | ```scala
173 | package scala
174 | object Predef {
175 | class ArrowAssoc[A](x: A) {
176 | def -> [B](y: B): Tuple2[A, B] = Tuple2(x, y)
177 | }
178 | implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] =
179 | new ArrowAssoc(x)
180 | ...
181 | }
182 |
183 | Map(1 -> "one", 2 -> "two", 3 -> "three")
184 | ```
185 |
186 | # Implicit classes
187 |
188 | - Syntactic sugar for defining a class together with an implicit conversion
189 |
190 | ```scala
191 | implicit class A(x: Int)
192 | ```
193 |
194 | becomes
195 |
196 | ```scala
197 | class A(x: Int)
198 | implicit def A(x: Int): A = new A(x)
199 | ```
200 | # Implicit classes
201 |
202 | ```scala
203 | // ISO 3166-1 alpha-2
204 | implicit class CountryCode(val code: String)
205 |
206 | def currencyFor(country: CountryCode): Currency = ???
207 |
208 | currencyFor(country = "BG")
209 | ```
210 |
211 | # Implicit classes - Rich wrapper pattern
212 |
213 | ```scala
214 | implicit class RichIterator[A](val iterator: Iterator[A]) {
215 | def zipWithNext: Iterator[(A, A)] = ...
216 | }
217 | ```
218 |
219 | # Implicit value classes
220 |
221 | ```scala
222 | implicit class RichInt(val i: Int) extends AnyVal {
223 | def squared: Int = i * i
224 | }
225 | ```
226 |
227 | # Implicit parameters
228 |
229 | Applicability:
230 | - Type classes
231 | - Context and config
232 | - Dependency injection
233 | - Proving theorems
234 | - ...
235 |
236 | # Implicit parameters
237 |
238 | ```scala
239 | implicit val currency: Currency = Currency.getInstance("BGN")
240 |
241 | def formattedText(price: BigDecimal)(implicit currency: Currency): String = ???
242 |
243 | val price: BigDecimal = ???
244 |
245 | formattedText(price)
246 |
247 | // Expanded
248 | formattedText(price)(currency)
249 | ```
250 |
251 | # Proving theorems
252 |
253 | ```scala
254 | /* if */ xs: List[List[A]]
255 | /* then */ xs.flatten: List[A]
256 | ```
257 |
258 | ```scala
259 | class List[A] {
260 | def flatten[B](implicit evidence: A =:= List[B]): List[B]
261 | }
262 | ```
263 |
264 | # Providing context using implicits
265 |
266 | # Don't use implicits for conveniently passing ordinary parameters around!
267 |
268 | # Traditional ways to express context
269 |
270 | # Imperative way
271 |
272 | - Global/shared mutable variables
273 | - Global/shared constants
274 | - Mutability is dangerous, constants are rigid
275 |
276 | # Functional way
277 |
278 | - Just pass anything you need as a parameter
279 | - Type safe
280 | - Sometimes gets tedious
281 | - Error prone
282 |
283 | # Scala way
284 |
285 | - Leave some parameters _implicit_
286 | - We provide just a type
287 | - Compiler provides the rest
288 |
289 | # Scala way
290 |
291 | ```scala
292 | implicit val executionContext: ExecutionContext = ???
293 |
294 | def queue(task: Task)(implicit ec: ExecutionContext): Future[Result] = ???
295 | ```
296 |
297 | ```scala
298 | class Viewers(val persons: Set[Person])
299 |
300 | def getScore(paper: Paper)(implicit viewers: Viewers): Int = {
301 | if (hasConflict(viewers, paper.authors)) -1
302 | else realScore(paper)
303 | }
304 |
305 | def getRanking(implicit viewers: Viewers): List[Paper] = {
306 | papers.sortBy(_.score)
307 | }
308 | ```
309 |
310 | # Cool stuff
311 |
312 | - `:implicits`
313 | - `-Xprint:typer`
314 | - `@implicitNotFound`
315 |
316 | # `Ordering` example
317 |
318 | # Supplying implicit values
319 |
320 | ```scala
321 | def implicitly[T]: T
322 | ```
323 |
324 | # Supplying implicit values
325 |
326 | ```scala
327 | def implicitly[T](implicit t: T): T = t
328 | ```
329 |
--------------------------------------------------------------------------------
/lectures/09-LazyEval.txt:
--------------------------------------------------------------------------------
1 | | \gLazy Evaluation!
2 |
3 |
4 | ---
5 | < \* \gSummary:
6 |
7 | < \*Strictness, non-strictness and laziness.
8 |
9 | < Short introduction to Streams.
10 |
11 | ---
12 | < \gStrictness and non-strictness.
13 |
14 | < To say a function is non-strict just means,
15 | < that the function may choose not to evaluate one or more of its arguments.
16 |
17 | < In contrast, a strict function always evaluates its arguments.
18 |
19 | ---
20 | < \*\gTypes of evaluation Short Demo
21 |
22 | < Evaluation by value
23 | < Evaluation by name
24 | < Lazy Evaluation
25 |
26 | ---
27 | < \*\gStrict evaluation
28 | ```
29 | def square(x :Int) = x*x;
30 |
31 | square(4+1)
32 | ```
33 | --
34 | ```
35 | square(sys.error("Done before started"))
36 | ```
37 |
38 | ---
39 | | \*\gNon-strictness
40 | < Short circuit operators.
41 | ```
42 | if(false && {print("Heavy Operation \n"); true }){
43 | def operation1 = {print ("Another Hour of my life")}
44 | operation1
45 | }
46 | ```
47 | --
48 | ```
49 | if(true && {print("Heavy Operation \n"); true }){
50 | def operation2 = {print ("Another Hour of my life \n")}
51 | operation2
52 | }
53 | ```
54 | ---
55 | | \gStrict evaluation
56 | ```
57 | def byValueRepresent(helpNeeded: Boolean)(work: String) = {
58 | println("Start")
59 | if (helpNeeded) {
60 | println("I need help");
61 | work
62 | println(" Work done! You are free to go\n");
63 | }
64 | else {
65 | println("Don't need help! Go Home Have a beer \n")
66 | }
67 | }
68 |
69 | def actualWork = {
70 | val work: String = "Heavy lifting Work! Back Pain!"
71 | println(work);
72 | work
73 | }
74 |
75 | byValueRepresent(false)(actualWork);
76 | ```
77 | --
78 | ```
79 | byValueRepresent(true)(actualWork);
80 | ```
81 |
82 | ---
83 | | \gNon-strictness Evaluation by name
84 |
85 | ```
86 | def byNameRepresent(helpNeeded: Boolean)(work: => String) = {
87 | println("Start")
88 | if (helpNeeded) {
89 | println("I need help!");
90 | work
91 | println(" Work done! You are free to go\n");
92 | }
93 | else {
94 | println("Don't need help! Go Home Have a beer \n")
95 | }
96 | }
97 |
98 | def byValueRepresent(helpNeeded: Boolean)(work: String) = {
99 | println("Start")
100 | if (helpNeeded) {
101 | println("I need help");
102 | work
103 | println(" Work done! You are free to go\n");
104 | }
105 | else {
106 | println("Don't need help! Go Home Have a beer \n")
107 | }
108 | }
109 |
110 | ```
111 | --
112 | ```
113 | byNameRepresent(false)(actualWork);
114 | ```
115 | --
116 | ```
117 | byNameRepresent(true)(actualWork);
118 | ```
119 | ---
120 | | \g Laziness
121 |
122 | ```
123 | def maybeTwice(b: Boolean, i: => Int) = if (b) i+i else 0
124 | val x = maybeTwice(true, { println("hi"); 1+41 })
125 | ```
126 | --
127 | ==================================================
128 | ```
129 | def maybeTwice1(b: Boolean, i: => Int) = {
130 | val j = i
131 | if (b) j + j else 0
132 | }
133 | val xy = maybeTwice1(true, { println("hi"); 1+41 })
134 | ```
135 | --
136 | ==================================================
137 | ```
138 | def maybeTwice2(b: Boolean, i: => Int) = {
139 | lazy val j = i
140 | if (b) j + j else 0
141 | }
142 | val xy = maybeTwice2(true, { println("hi"); 1+41 })
143 |
144 | ```
145 | ---
146 | | \g Example
147 |
148 | ```
149 | def func1: Int = {
150 |
151 | val x = {
152 | print("x:");
153 | 1
154 | }
155 | lazy val y = {
156 | print("y:");
157 | 2
158 | }
159 | def z = {
160 | print("z:");
161 | 3
162 | }
163 |
164 | z + y + x + z + y + x
165 | }
166 |
167 | func1
168 | ```
169 | ---
170 | | \g End Part One.
171 |
172 |
--------------------------------------------------------------------------------
/lectures/09-Streems.txt:
--------------------------------------------------------------------------------
1 | | \g Streems!
2 |
3 |
4 | ---
5 |
6 | << \g Normal List combinator's.
7 | ```
8 | import scala.annotation.tailrec
9 | import scala.collection.immutable.Stream
10 |
11 | val list1 = List(2, 3, 4, 5, 6, 7, 8, 9)
12 | val list2 = List(1,2,3,4).map{x=>println(x);x + 10}.map({y=>println(y);y + 10})
13 |
14 | ```
15 | --
16 | ```
17 | val simpleStream = (10 to 10000).toStream.map(x => {println("dd " + (x + 10));x+10})
18 | val simpleStream1 = simpleStream.take(3)
19 | --
20 |
21 | simpleStream1.force
22 | --
23 | simpleStream1
24 | --
25 | simpleStream.map(_ * 3).take(5).count(x => true)
26 |
27 | simpleStream.take(5).size
28 |
29 | ```
30 | ---
31 | ```
32 | val listOfElem = (10 to 10000)
33 | --
34 | val simpleView = (1 to 10000).view
35 |
36 | val streamElem : Stream[Int] = Stream.from(1)
37 | val simpleView1 = streamElem.view
38 |
39 | val viewCombinator = simpleView.map(x=>{println(s"view $x;");x})
40 | val streamCombinator = streamElem.map(x=>{println(s"stream $x;");x})
41 |
42 | --
43 | val viewVal1 = viewCombinator.take(1).size
44 | --
45 | val viewVal2 = viewCombinator.take(1).size
46 | --
47 | val viewVal3 = viewCombinator.take(2).size
48 | --
49 | val viewVal4 = viewCombinator.take(3).size
50 | --
51 | val viewVal5 = viewCombinator.take(4).size
52 | --
53 | val viewVal6 = viewCombinator.take(5).size
54 | --
55 | val viewVal7 = viewCombinator.take(6).force
56 |
57 | --
58 | val streamHead1 = streamCombinator.take(1).size
59 | --
60 | val streamHead2 = streamCombinator.take(1).size
61 | --
62 | val streamHead3 = streamCombinator.take(2).size
63 | --
64 | val streamHead4 = streamCombinator.take(2).size
65 | --
66 | val streamHead5 = streamCombinator.take(3).force
67 |
68 |
69 | ```
70 | ---
71 |
72 | ```
73 | import scala.annotation.tailrec
74 | import scala.collection.immutable.Stream
75 | @tailrec
76 | def findFirst1(check :(Int)=>Boolean, ls:List[Int]): (Int,List[Int])={
77 | ls match {
78 | case Nil => println("Bad Luck"); sys.error("Bad Luck")
79 | case x::xs =>if(check(x)){(x, xs)}else findFirst1(check, xs)
80 | }
81 | }
82 | --
83 |
84 | @tailrec
85 | def findFirst2(check: (Int) => Boolean, ls: Stream[Int]): (Int, Stream[Int]) = {
86 | ls match {
87 | case Stream.Empty => println("Bad Luck"); sys.error("Bad Luck")
88 | case x #:: xs =>if (check(x) ){ (x, xs)}else findFirst2(check, xs)
89 | }
90 | }
91 |
92 | --
93 | val listOfElem = (10 to 10000)
94 | --
95 | val fFirst1 = findFirst1({ (x) => x > 14 }, listOfElem.toList)
96 | --
97 |
98 | val streamElem : Stream[Int] = Stream.from(1)
99 | val fFirst2 = findFirst2({ (x) => x > 14 }, streamElem)
100 |
101 | ```
102 |
103 | ---
104 | ```
105 | lazy val fibs: Stream[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map(n => n._1 + n._2 )
106 | val fib = fibs.take(5).force
107 | ```
108 |
109 | ---
110 |
111 | | \g End Part Two.
112 |
--------------------------------------------------------------------------------
/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/01-intro/HelloWorld.scala:
--------------------------------------------------------------------------------
1 | object HelloWorld {
2 | def main(args: Array[String]) {
3 | println("Hello, world!")
4 | }
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/lectures/examples/01-intro/sbt-hello-world/build.sbt:
--------------------------------------------------------------------------------
1 | name := "hello-world"
2 | version := "0.1"
3 |
4 | libraryDependencies ++= Seq(
5 | "org.scalatest" %% "scalatest" % "3.0.5" % "test"
6 | )
7 |
8 |
--------------------------------------------------------------------------------
/lectures/examples/01-intro/sbt-hello-world/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.6
2 |
--------------------------------------------------------------------------------
/lectures/examples/01-intro/sbt-hello-world/src/main/scala/HelloWorld.scala:
--------------------------------------------------------------------------------
1 | object HelloWorld {
2 | def main(args: Array[String]): Unit = {
3 | println("Hello, World!")
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/lectures/examples/01-intro/sbt-hello-world/src/main/scala/Lists.scala:
--------------------------------------------------------------------------------
1 | object Lists {
2 | def sum(xs: List[Int]): Int = xs.foldLeft(0)(_ + _)
3 | }
4 |
--------------------------------------------------------------------------------
/lectures/examples/01-intro/sbt-hello-world/src/main/scala/Worksheet.sc:
--------------------------------------------------------------------------------
1 | HelloWorld.main(Array.empty)
2 |
3 | val a = 1 + 1
4 |
5 | a + 3
--------------------------------------------------------------------------------
/lectures/examples/01-intro/sbt-hello-world/src/test/scala/ExampleSpec.scala:
--------------------------------------------------------------------------------
1 | import org.scalatest._
2 |
3 | class ExampleSpec extends FlatSpec with Matchers {
4 | "+" should "sum two numbers" in {
5 | 2 + 3 should be (5)
6 | }
7 |
8 | "Lists sum" should "correctly sum nonempty List" in {
9 | val nonEmptyList = List(1, 2, 3)
10 |
11 | Lists.sum(nonEmptyList) should be (6)
12 | }
13 |
14 | "Lists sum" should "return zero for empty List" in {
15 | val emptyList = List.empty[Int]
16 |
17 | Lists.sum(emptyList) should be (0)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lectures/examples/03-oop/caseclass.scala:
--------------------------------------------------------------------------------
1 | package caseclass
2 |
3 | trait Animal {
4 | val order: String
5 | }
6 |
7 | case class Mammal(order: String = "rodentia", milk: Boolean = true) extends Animal
8 |
9 | object GotMilk {
10 | def main(args: Array[String]):Unit = {
11 | val mickeyMouse = Mammal()
12 | val anyMouse = Mammal()
13 |
14 | val amy = mickeyMouse.copy(order = "primates")
15 |
16 | println(s"I am mickey and I am ${if (mickeyMouse == anyMouse) "ordinary" else "special"}!")
17 | println(s"And I am amy and I am a ${amy.order}")
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lectures/examples/03-oop/companion.scala:
--------------------------------------------------------------------------------
1 | package companion
2 |
3 | class Animal
4 |
5 | class Mammal(val order: String) extends Animal {
6 | private def milk: Boolean = true
7 | }
8 |
9 | object Mammal {
10 | private[companion] val RODENT = "rodentia"
11 | private[companion] val PRIMATE = "primates"
12 |
13 | def apply(): Mammal = new Mammal(RODENT)
14 | def apply(order: String): Mammal = new Mammal(order)
15 |
16 | def gotMilk(m:Mammal): Boolean = m.milk
17 | }
18 |
19 | object GotMilk {
20 | def main(args: Array[String]):Unit = {
21 | val mickeyMouse = Mammal()
22 | val amyTheGorilla = Mammal(Mammal.PRIMATE)
23 |
24 | println(s"My order is ${mickeyMouse.order} and I have all the milk: ${Mammal.gotMilk(mickeyMouse)}")
25 | println(s"My order is ${amyTheGorilla.order} and I have all the milk: ${Mammal.gotMilk(amyTheGorilla)}")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lectures/examples/03-oop/oop.scala:
--------------------------------------------------------------------------------
1 | package oop
2 |
3 | class Animal
4 |
5 | class Mammal(val order: String) extends Animal {
6 | var milk: Boolean = true
7 | val theMilk: Boolean = true
8 | def zeMilk: Boolean = true
9 | }
10 |
11 | object GotMilk {
12 | def main(args: Array[String]):Unit = {
13 | val mickeyMouse = new Mammal("rodentia")
14 | val amyTheGorilla = new Mammal("primates")
15 |
16 | println(s"My order is ${mickeyMouse.order} and I have all the milk ${mickeyMouse.milk} ${mickeyMouse.theMilk} ${mickeyMouse.zeMilk}")
17 | println(s"My order is ${amyTheGorilla.order} and I have all the milk ${amyTheGorilla.milk} ${amyTheGorilla.theMilk} ${amyTheGorilla.zeMilk}")
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lectures/examples/03-oop/traits.scala:
--------------------------------------------------------------------------------
1 | package traits
2 |
3 | class Animal
4 |
5 | trait Edible {
6 | def isPoisonous: Boolean
7 | def isDelicious: Boolean
8 |
9 | def ICanHazEat(): Boolean = {
10 | !isPoisonous && isDelicious
11 | }
12 | }
13 |
14 | class Mammal(val order: String, val isDelicious: Boolean = true, val isPoisonous: Boolean = false) extends Animal with Edible {
15 | private def milk: Boolean = true
16 | }
17 |
18 | object Mammal {
19 | private[traits] val RODENT = "rodentia"
20 | private[traits] val PRIMATE = "primates"
21 |
22 | def apply(): Mammal = new Mammal(RODENT)
23 | def apply(order: String): Mammal = new Mammal(order)
24 | def apply(order: String, isDelicious: Boolean = true, isPoisonous: Boolean = false): Mammal = new Mammal(order, isDelicious, isPoisonous)
25 |
26 | def gotMilk(m:Mammal): Boolean = m.milk
27 | }
28 |
29 | object GotMilk {
30 | def main(args: Array[String]):Unit = {
31 | val mickeyMouse = Mammal()
32 | val amyTheGorilla = Mammal(order = Mammal.PRIMATE, isPoisonous = true)
33 |
34 | println(amyTheGorilla.order)
35 | println(s"My order is ${mickeyMouse.order} and I am ${if (mickeyMouse.ICanHazEat) "delicious" else "disgusting"}")
36 | println(s"My order is ${amyTheGorilla.order} and I am ${if (amyTheGorilla.ICanHazEat) "delicious" else "disgusting"}")
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lectures/examples/09-stream/FStream.scala:
--------------------------------------------------------------------------------
1 |
2 | sealed trait FStream[+A] {
3 |
4 | def head():A ={
5 | this match {
6 | case FEmpty => ???
7 | case FCons(h, _) => h
8 | }
9 | }
10 |
11 | def tail(): FStream[A] = {
12 | this match {
13 | case FEmpty => ???
14 | case FCons(_, tl) => tl()
15 | }
16 | }
17 |
18 | def take(n: Int): FStream[A] = {
19 | this match {
20 | case FCons(h, tl) if (n > 0) => FCons(h, () => tl().take(n - 1))
21 | case _ => FEmpty
22 | }
23 | }
24 |
25 | }
26 |
27 | case object FEmpty extends FStream[Nothing]
28 | case class FCons[+A](h: A, tl:() => FStream[A])extends FStream[A]
29 |
30 |
31 | object FStream {
32 |
33 | def con[A](head: => A, tail: => FStream[A]) :FStream[A] = {
34 | val h = head
35 | lazy val tl = tail
36 | FCons(h, () => tl)
37 | }
38 |
39 | def apply[A](a:A*): FStream[A] = {
40 | if(a.isEmpty) FEmpty
41 | else con(a.head, apply(a.tail:_*))
42 | }
43 |
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/build.sbt:
--------------------------------------------------------------------------------
1 | name := "concurrency"
2 | version := "0.1"
3 |
4 | libraryDependencies ++= Seq(
5 | "io.monix" %% "monix" % "2.3.3",
6 | "com.typesafe.akka" %% "akka-actor" % "2.5.22",
7 | "com.typesafe.akka" %% "akka-actor-typed" % "2.5.22",
8 | "com.typesafe.akka" %% "akka-stream" % "2.5.22",
9 | "com.typesafe.akka" %% "akka-http" % "10.1.5",
10 | "org.asynchttpclient" % "async-http-client" % "2.8.1",
11 | "org.scalatest" %% "scalatest" % "3.0.5" % "test"
12 | )
13 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.6
2 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/actors/ActorsExample1.scala:
--------------------------------------------------------------------------------
1 | package actors
2 |
3 | import akka.actor.{Actor, ActorSystem, Props}
4 |
5 | class Human extends Actor {
6 | def receive: Receive = {
7 | case "Hello" =>
8 | println("Hello, how are you?")
9 | }
10 | }
11 |
12 | object ActorsExample1 extends App {
13 | val actorSystem = ActorSystem()
14 |
15 | val george = actorSystem.actorOf(Props[Human])
16 |
17 | george ! "Hello"
18 | }
19 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/actors/ActorsExample2.scala:
--------------------------------------------------------------------------------
1 | package actors
2 |
3 | import akka.actor.{Actor, ActorRef, ActorSystem, Props}
4 |
5 | object Counter {
6 | trait CounterProtocol
7 | case class IncreaseCountWith(value: Int) extends CounterProtocol
8 | case object GetCount extends CounterProtocol
9 | case class Count(value: Int, numberOfIncreases: Int) extends CounterProtocol
10 |
11 | }
12 |
13 | class Counter extends Actor {
14 | import Counter._
15 |
16 | def receive: Receive = countBehaviour(0, 0)
17 |
18 | def countBehaviour(currentCount: Int, numberOfIncreases: Int): Receive = {
19 | case IncreaseCountWith(n) =>
20 | context.become(countBehaviour(currentCount + n, numberOfIncreases + 1))
21 | case GetCount =>
22 | sender ! Count(currentCount, numberOfIncreases)
23 | }
24 | }
25 |
26 | class Worker(counter: ActorRef) extends Actor {
27 | import Counter._
28 |
29 | def receive: Receive = {
30 | case "Start" =>
31 | println("Starting work...")
32 |
33 | counter ! IncreaseCountWith(1)
34 | counter ! IncreaseCountWith(2)
35 | counter ! IncreaseCountWith(3)
36 | counter ! GetCount
37 | counter ! IncreaseCountWith(4)
38 | counter ! IncreaseCountWith(5)
39 | counter ! IncreaseCountWith(6)
40 | counter ! GetCount
41 |
42 | println("Work finished")
43 |
44 | case Count(n, times) =>
45 | println(s"Current counter is $n, has been increased $times times")
46 | }
47 | }
48 |
49 | object ActorsExample2 extends App {
50 | val actorSystem = ActorSystem()
51 |
52 | val counter = actorSystem.actorOf(Props[Counter])
53 | val worker = actorSystem.actorOf(Props(new Worker(counter)))
54 |
55 | worker ! "Start"
56 | }
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/actors/ActorsExample3.scala:
--------------------------------------------------------------------------------
1 | package actors
2 |
3 | import actors.Person.Greet
4 | import akka.actor.{Actor, ActorRef, ActorSystem, Props}
5 |
6 | object Person {
7 | sealed trait PersonProtocol
8 | case class Greet(person: ActorRef)
9 | case object Hello
10 | case object WhatsYourName
11 | case class NameReply(name: String)
12 |
13 | def props(name: String) = Props(new Person(name))
14 | }
15 |
16 | class Person(name: String) extends Actor {
17 | import Person._
18 |
19 | def receive: Receive = start
20 |
21 | def start: Receive = {
22 | case Greet(person) =>
23 | person ! Hello
24 |
25 | context.become(sentGreetings(person))
26 | case Hello =>
27 | println(s"$name was greeted")
28 |
29 | sender ! Hello
30 |
31 | context.become(greeted(sender))
32 | }
33 |
34 | def sentGreetings(to: ActorRef): Receive = {
35 | case Hello =>
36 | println(s"$name was greeted")
37 |
38 | sender ! WhatsYourName
39 |
40 | case NameReply(otherPersonName) =>
41 | if (sender == to)
42 | println(s"$name received other person name: $otherPersonName")
43 | else
44 | println(s"A stranger introduced themselves to me ($name)")
45 |
46 | context.become(start)
47 | }
48 |
49 | def greeted(by: ActorRef): Receive = {
50 | case WhatsYourName =>
51 | if (sender == by) {
52 | println(s"$name was asked for name")
53 |
54 | sender ! NameReply(name)
55 | } else println(s"$name asked for name by a stranger")
56 |
57 | context.become(start)
58 | }
59 | }
60 |
61 | object ActorsExample extends App {
62 | val actorSystem = ActorSystem()
63 |
64 | val george = actorSystem.actorOf(Person.props("George"))
65 | val john = actorSystem.actorOf(Person.props("John"))
66 |
67 | george ! Greet(john)
68 | }
69 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/actors/AkkaTypedExample.scala:
--------------------------------------------------------------------------------
1 | package actors
2 |
3 | import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
4 | import akka.actor.typed.{ActorSystem, Behavior}
5 |
6 | object PrintMyActorRefActor {
7 | def apply(): Behavior[String] =
8 | Behaviors.setup(context => new PrintMyActorRefActor(context))
9 | }
10 |
11 | class PrintMyActorRefActor(context: ActorContext[String]) extends AbstractBehavior[String] {
12 |
13 | override def onMessage(msg: String): Behavior[String] =
14 | msg match {
15 | case "printit" =>
16 | val secondRef = context.spawn(Behaviors.empty[String], "second-actor")
17 | println(s"Second: $secondRef")
18 | this
19 | }
20 | }
21 |
22 | object Main {
23 | def apply(): Behavior[String] =
24 | Behaviors.setup(context => new Main(context))
25 |
26 | }
27 |
28 | class Main(context: ActorContext[String]) extends AbstractBehavior[String] {
29 | override def onMessage(msg: String): Behavior[String] =
30 | msg match {
31 | case "start" =>
32 | val firstRef = context.spawn(PrintMyActorRefActor(), "first-actor")
33 | println(s"First: $firstRef")
34 | firstRef ! "printit"
35 | this
36 | }
37 | }
38 |
39 | object ActorHierarchyExperiments extends App {
40 | ActorSystem(Main(), "testSystem") ! "start"
41 | }
42 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/callbacks/Callbacks.scala:
--------------------------------------------------------------------------------
1 | package callbacks
2 |
3 | import java.util.concurrent.{Executor, Executors}
4 |
5 | case class Product(name: String, kind: String)
6 | case class Verification(quality: Int)
7 |
8 | object Callbacks {
9 | val threadPool: Executor = Executors.newFixedThreadPool(Runtime.getRuntime.availableProcessors)
10 | def execute(work: => Any): Unit = threadPool.execute(() => work)
11 |
12 | def produceProduct(onComplete: Product => Unit): Unit = execute {
13 | val productId = Thread.currentThread().getId
14 |
15 | println(s"Producing product $productId...")
16 | Thread.sleep(2000)
17 | println(s"Product produced, $productId")
18 |
19 | execute(onComplete(Product(s"Product $productId", "Kind")))
20 | }
21 |
22 | def produce2Products(onComplete: (Product, Product) => Unit): Unit = {
23 | var firstProduct: Option[Product] = None
24 |
25 | // Needs mutability and manual synchronization
26 | def callback(product: Product): Unit = this.synchronized {
27 | firstProduct match {
28 | case Some(first) => onComplete(first, product)
29 | case None => firstProduct = Some(product)
30 | }
31 | }
32 |
33 | produceProduct(callback)
34 | produceProduct(callback)
35 |
36 | println("Work started")
37 | }
38 |
39 | def main(args: Array[String]): Unit = execute {
40 | produce2Products(println(_, _))
41 | }
42 |
43 | def verifyProduct(product: Product)(onVerified: Verification => Unit): Unit = execute {
44 | val threadId = Thread.currentThread().getId
45 |
46 | println(s"Verifying product ${product.name}...")
47 | Thread.sleep(2000)
48 | println(s"Product verified, thread: $threadId")
49 |
50 | execute(onVerified(Verification(threadId.hashCode)))
51 | }
52 |
53 | def produceInPipeline(callback: (List[Product], List[Verification]) => Unit): Unit = {
54 | // Callback hell
55 | produceProduct { a =>
56 | verifyProduct(a) { aVerification =>
57 | produceProduct { b =>
58 | verifyProduct(b) { bVerification =>
59 | callback(List(a, b), List(aVerification, bVerification))
60 | }
61 | }
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/callbacks/FutureApp.scala:
--------------------------------------------------------------------------------
1 | package callbacks
2 |
3 | import scala.concurrent.Future
4 |
5 | object FutureApp extends App {
6 | import scala.concurrent.ExecutionContext.Implicits.global
7 |
8 | def double(n: Int): Future[Int] = Future {
9 | n * 2
10 | }
11 |
12 | val calc1 = Future {
13 | println("calc1")
14 | Thread.sleep(2000)
15 | 1 + 1
16 | }
17 | val calc2 = Future {
18 | println("calc2")
19 | Thread.sleep(2000)
20 | 42 * 20
21 | }
22 |
23 | val result = for {
24 | (a, b) <- calc1.zip(calc2)
25 | c <- double(a + b)
26 | } yield c.toString
27 |
28 | result.foreach(println)
29 |
30 | Thread.sleep(4000)
31 | }
32 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/concurrent/Executors.scala:
--------------------------------------------------------------------------------
1 | package concurrent
2 |
3 | import java.util.concurrent.{Executor, ForkJoinPool}
4 |
5 | object Executors {
6 | implicit val defaultExecutor: Executor = new ForkJoinPool
7 |
8 | val currentThreadExecutor = new Executor {
9 | override def execute(operation: Runnable): Unit = operation.run()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/concurrent/FailSafeExample.scala:
--------------------------------------------------------------------------------
1 | package concurrent
2 |
3 | import scala.concurrent.ExecutionContext.Implicits.global
4 | import scala.concurrent.{Await, Future}
5 | import scala.concurrent.duration._
6 |
7 | object FailSafeExample extends App {
8 | def double(n: Int, shouldFail: Boolean = false): Future[Int] = Future {
9 | println(s"Executing double with $n")
10 |
11 | if (shouldFail) throw new RuntimeException
12 |
13 | n * 2
14 | }
15 |
16 | val execution = for {
17 | a <- double(2)
18 | b <- double(a, true)
19 | c <- double(b)
20 | } yield c
21 |
22 | val additionOperation = execution.map(_ + 10)
23 |
24 | val result = Await.result(additionOperation, 2.seconds)
25 |
26 | println(result)
27 | }
28 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/concurrent/impl/Future.scala:
--------------------------------------------------------------------------------
1 | package concurrent.impl
2 |
3 | import java.util.concurrent.Executor
4 |
5 | import concurrent.Executors
6 |
7 | import scala.concurrent.duration.Duration
8 | import scala.concurrent.{Awaitable, CanAwait}
9 | import scala.util.control.NonFatal
10 | import scala.util.{Failure, Success, Try}
11 |
12 | trait Future[+A] extends Awaitable[A] {
13 | def value: Option[Try[A]]
14 | def onComplete(handler: Try[A] => Unit)(implicit ex: Executor): Unit
15 |
16 | def isComplete: Boolean = value.isDefined
17 |
18 | def map[B](f: A => B)(implicit ex: Executor): Future[B] = {
19 | val p = Promise[B]
20 | onComplete(value => p.complete(value.map(f)))
21 | p.future
22 | }
23 |
24 | def flatMap[B](f: A => Future[B])(implicit ex: Executor): Future[B] = {
25 | val p = Promise[B]
26 | onComplete {
27 | case Success(value) => Future.tryF(f(value)).onComplete(p.complete)
28 | case Failure(e) => p.fail(e)
29 | }
30 | p.future
31 | }
32 |
33 | def zip[B](fb: Future[B]): Future[(A, B)] = {
34 | implicit val ex = Executors.currentThreadExecutor
35 |
36 | for {
37 | a <- this
38 | b <- fb
39 | } yield (a, b)
40 | }
41 |
42 | def map2[B, R](fb: Future[B])(f: (A, B) => R)(implicit ex: Executor): Future[R] = zip(fb).map(f.tupled)
43 |
44 | def filter(f: A => Boolean)(implicit ex: Executor): Future[A] = {
45 | val p = Promise[A]
46 | onComplete(value => p.complete(value.filter(f)))
47 | p.future
48 | }
49 |
50 | def withFilter(f: A => Boolean)(implicit ex: Executor): Future[A] = filter(f)
51 |
52 | def recover[B >: A](f: PartialFunction[Throwable, B])(implicit ex: Executor): Future[B] = {
53 | val p = Promise[B]
54 | onComplete(value => p.complete(value.recover(f)))
55 | p.future
56 | }
57 |
58 | def recoverWith[B >: A](f: PartialFunction[Throwable, Future[B]])(implicit ex: Executor): Future[B] = {
59 | val p = Promise[B]
60 | onComplete {
61 | case Success(value) => p succeed value
62 | case Failure(e) =>
63 | if (f.isDefinedAt(e)) Future.tryF(f(e)) onComplete { p complete _ }
64 | else p.fail(e)
65 | }
66 | p.future
67 | }
68 |
69 | def foreach(f: A => Unit)(implicit ex: Executor): Unit = onComplete(_.foreach(f))
70 | }
71 |
72 | object Future {
73 | def apply[A](value: => A)(implicit ex: Executor) = {
74 | val p = Promise[A]
75 | ex.execute(() => p.succeed(value))
76 | p.future
77 | }
78 | def successful[A](value: A) = resolved(Success(value))
79 | def failed[A](e: Throwable) = resolved(Failure(e))
80 |
81 | def tryF[A](f: => Future[A]) = try f catch { case NonFatal(e) => Future.failed(e) }
82 |
83 | def resolved[A](r: Try[A]) = new Future[A] {
84 | val value: Option[Try[A]] = Some(r)
85 | def onComplete(handler: Try[A] => Unit)(implicit ex: Executor): Unit = ex.execute(() => handler(r))
86 |
87 | def ready(atMost: Duration)(implicit permit: CanAwait): this.type = this
88 | def result(atMost: Duration)(implicit permit: CanAwait): A = r.get
89 | }
90 |
91 | def firstOf[A](futures: Seq[Future[A]])(implicit ex: Executor) = {
92 | val p = Promise[A]
93 | futures.foreach(_.onComplete(p.complete))
94 | p.future
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/concurrent/impl/Promise.scala:
--------------------------------------------------------------------------------
1 | package concurrent.impl
2 |
3 | import java.util.concurrent.Executor
4 | import java.util.concurrent.atomic.AtomicReference
5 | import java.util.concurrent.locks.LockSupport
6 |
7 | import concurrent.Executors
8 |
9 | import scala.annotation.tailrec
10 | import scala.concurrent.duration.Duration
11 | import scala.concurrent.{CanAwait, TimeoutException}
12 | import scala.util.{Failure, Success, Try}
13 |
14 | class Promise[A] {
15 | case class Handler(handler: Try[A] => Any, ex: Executor) {
16 | def executeWithValue(value: Try[A]): Unit = ex.execute(() => handler(value))
17 | }
18 |
19 | sealed trait State
20 | case class Completed(value: Try[A]) extends State
21 | case class Pending(handlers: List[Handler]) extends State
22 |
23 | // Most of the time AtomicReference is better for synchronization as it uses non-blocking techniques.
24 | // Instead of forcing threads to be suspended while waiting on a lock, here there are no locks involved.
25 | // Instead CPU's comapareAndSet operation is set. It can be used to atomically set a new value if the current
26 | // ones matches the expected current one passed to the compareAndSet method.
27 | //
28 | // compareAndSet returns a Boolean, true if the update completed, false otherwise.
29 | // If the update has failed then it can be retried by the user, first reading the current value again
30 | // (as the failure indicated it has been updated by some other thread) and then reapplying the calculation on that value.
31 | //
32 | // This technique is called optimistic locking. See [executeWhenComplete] and [completeWithValue] for how it can be applied.
33 | //
34 | // AtomicReference keeps its value in a volatile variable internally.
35 | private val state = new AtomicReference[State](Pending(List.empty))
36 |
37 | @tailrec
38 | private def executeWhenComplete(handler: Handler): Unit = state.get() match {
39 | case Completed(value) => handler.executeWithValue(value)
40 | case s @ Pending(handlers) =>
41 | val newState = Pending(handler :: handlers)
42 | if (!state.compareAndSet(s, newState)) executeWhenComplete(handler)
43 | }
44 |
45 | @tailrec
46 | private def completeWithValue(value: Try[A]): List[Handler] = state.get() match {
47 | case Completed(_) => List.empty
48 | case s @ Pending(handlers) =>
49 | if (state.compareAndSet(s, Completed(value))) handlers
50 | else completeWithValue(value)
51 | }
52 |
53 | val future: Future[A] = new Future[A] {
54 | def value: Option[Try[A]] = state.get() match {
55 | case Completed(value) => Some(value)
56 | case _ => None
57 | }
58 |
59 | def onComplete(handler: Try[A] => Unit)(implicit ex: Executor): Unit = executeWhenComplete(Handler(handler, ex))
60 |
61 | def ready(atMost: Duration)(implicit permit: CanAwait): this.type = {
62 | if (!isComplete && Duration.Zero < atMost) {
63 | val thread = Thread.currentThread
64 | onComplete(_ => LockSupport.unpark(thread))(Executors.currentThreadExecutor)
65 |
66 | if (atMost == Duration.Inf) LockSupport.park()
67 | else LockSupport.parkNanos(atMost.toNanos)
68 | }
69 |
70 | if (isComplete) this
71 | else throw new TimeoutException
72 | }
73 |
74 | def result(atMost: Duration)(implicit permit: CanAwait): A = ready(atMost).value.get.get
75 | }
76 |
77 | def complete(value: Try[A]): Promise[A] = {
78 | completeWithValue(value).foreach(_.executeWithValue(value))
79 | this
80 | }
81 |
82 | def succeed(value: A): Promise[A] = complete(Success(value))
83 |
84 | def fail(e: Throwable): Promise[A] = complete(Failure(e))
85 | }
86 |
87 | object Promise {
88 | def apply[T] = new Promise[T]
89 | }
90 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/concurrent/lecture/Future.scala:
--------------------------------------------------------------------------------
1 | package concurrent.lecture
2 |
3 | import java.util.concurrent.Executor
4 |
5 | import concurrent.Executors
6 |
7 | import scala.util.{Failure, Success, Try}
8 |
9 | // The implementation from the lecture
10 | trait Future[+A] {
11 | def value: Option[Try[A]]
12 | def isCompleted = value.isDefined
13 |
14 | def onComplete(f: Try[A] => Any)(implicit ex: Executor): Unit
15 | def result: Try[A]
16 |
17 | def map[B](f: A => B)(implicit ex: Executor): Future[B] = {
18 | val p = Promise[B]
19 |
20 | onComplete(result => {
21 | val newValue = result.map(f)
22 | p.complete(newValue)
23 | })
24 |
25 | p.future
26 | }
27 | def flatMap[B](f: A => Future[B])(implicit ex: Executor): Future[B] = {
28 | val p = Promise[B]
29 | onComplete {
30 | case Success(value) =>
31 | Try(f(value)).fold(e => Future.failed(e), identity).onComplete(p.complete)
32 | case Failure(e) => p.fail(e)
33 | }
34 | p.future
35 | }
36 |
37 |
38 | def filter(f: A => Boolean)(implicit ex: Executor): Future[A] = {
39 | val p = Promise[A]
40 | onComplete(value => p.complete(value.filter(f)))
41 | p.future
42 | }
43 |
44 | // required for for comprehensions's if and pattern matching uses
45 | def withFilter(f: A => Boolean)(implicit ex: Executor): Future[A] = filter(f)
46 |
47 | def zip[B](fb: Future[B]): Future[(A, B)] = {
48 | implicit val ex = Executors.currentThreadExecutor
49 |
50 | for {
51 | a <- this
52 | b <- fb
53 | } yield (a, b)
54 | }
55 |
56 | def zipMap[B, R](fb: Future[B])(f: (A, B) => R)(implicit ex: Executor): Future[R] = this.zip(fb).map(f.tupled)
57 |
58 | def recover[B >: A](f: PartialFunction[Throwable, B])(implicit ex: Executor): Future[B] = {
59 | val p = Promise[B]
60 | onComplete(value => p.complete(value.recover(f)))
61 | p.future
62 | }
63 |
64 | def recoverWith[B >: A](f: PartialFunction[Throwable, Future[B]])(implicit ex: Executor): Future[B] = {
65 | val p = Promise[B]
66 | onComplete {
67 | case Success(value) => p succeed value
68 | case Failure(e) =>
69 | if (f.isDefinedAt(e))
70 | Try(f(e)).fold(e => Future.failed(e), identity).onComplete(p.complete)
71 | else p.fail(e)
72 | }
73 | p.future
74 | }
75 | }
76 |
77 | object Future {
78 | def apply[A](value: => A)(implicit ex: Executor): Future[A] = {
79 | val p = Promise[A]
80 |
81 | ex.execute(() => {
82 | p.complete(Try(value))
83 | })
84 |
85 | p.future
86 | }
87 |
88 | def successful[A](value: A) = resolved(Success(value))
89 | def failed[A](e: Throwable) = resolved(Failure(e))
90 |
91 | def resolved[A](r: Try[A]) = new Future[A] {
92 | val value: Option[Try[A]] = Some(r)
93 | def onComplete(f: Try[A] => Any)(implicit ex: Executor): Unit = ex.execute(() => f(r))
94 | def result: Try[A] = r
95 | }
96 | }
97 |
98 | object FutureApp extends App {
99 | import concurrent.Executors.defaultExecutor
100 |
101 | def double(n: Int): Future[Int] = Future {
102 | n * 2
103 | }
104 |
105 | val calc1 = Future { 1 + 1 }
106 | val calc2 = Future { 42 * 20 }
107 |
108 | val composed = for {
109 | (a, b) <- calc1.zip(calc2)
110 | c <- double(a + b)
111 | } yield c.toString
112 |
113 | println(composed.result)
114 | }
115 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/concurrent/lecture/Promise.scala:
--------------------------------------------------------------------------------
1 | package concurrent.lecture
2 |
3 | import java.util.concurrent.Executor
4 | import java.util.concurrent.locks.LockSupport
5 |
6 | import concurrent.Executors
7 |
8 | import scala.annotation.tailrec
9 | import scala.util.{Failure, Success, Try}
10 |
11 | class Promise[A] { self => // this allows us to rename the "this" reference so that we can use it correctly in the internal Future object bellow
12 | case class Handler(handler: Try[A] => Any, ex: Executor) {
13 | def executeWithValue(value: Try[A]): Unit = ex.execute(() => handler(value))
14 | }
15 |
16 | sealed trait State
17 | case class Completed(value: Try[A]) extends State
18 | case class Pending(handlers: List[Handler]) extends State
19 |
20 | @volatile private var state: State = Pending(List.empty)
21 |
22 | def complete(value: Try[A]): Unit = {
23 | val pendingHandlers = self.synchronized {
24 | state match {
25 | case Completed(_) => List.empty
26 | case Pending(handlers) =>
27 | state = Completed(value)
28 | handlers
29 | }
30 | }
31 |
32 | // execute them outside the lock
33 | pendingHandlers.foreach(_.executeWithValue(value))
34 | }
35 |
36 | def succeed(value: A): Unit = complete(Success(value))
37 | def fail(ex: Throwable): Unit = complete(Failure(ex))
38 |
39 | def future: Future[A] = new Future[A] {
40 | def onComplete(f: Try[A] => Any)
41 | (implicit ex: Executor): Unit = {
42 | val newHandler = Handler(f, ex)
43 |
44 | val toBeExecutedWith: Option[Try[A]] = self.synchronized {
45 | state match {
46 | case Completed(value) => Some(value)
47 | case Pending(handlers) =>
48 | state = Pending(newHandler :: handlers)
49 | None
50 | }
51 | }
52 |
53 | // execute outside the lock if the state was completed
54 | toBeExecutedWith.foreach(newHandler.executeWithValue)
55 | }
56 |
57 | def value: Option[Try[A]] = state match {
58 | case Completed(value) => Some(value)
59 | case _ => None
60 | }
61 |
62 | @tailrec
63 | def result: Try[A] = state match {
64 | case Completed(value) => value
65 | case _ =>
66 | val thread = Thread.currentThread()
67 |
68 | onComplete { _ =>
69 | // unpark will wake the thread if it's parked. Otherwise its next call to park will complete right away
70 | LockSupport.unpark(thread)
71 | }(Executors.currentThreadExecutor)
72 |
73 | // The tread will be suspended until another threads calls `unpark`, i.e. the future is completed
74 | LockSupport.park()
75 |
76 | result
77 | }
78 | }
79 | }
80 |
81 | object Promise{
82 | def apply[A]: Promise[A] = new Promise[A]
83 | }
84 |
85 | object PromiseApp {
86 | def doHttpCall(url: String): Future[Int] = {
87 | val p = Promise[Int]
88 |
89 | // doing http call
90 | // ...
91 |
92 | // after finished
93 | p.complete(Try(42))
94 |
95 | p.future
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/http/FutureWebServer.scala:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import java.util.NoSuchElementException
4 |
5 | import akka.actor.ActorSystem
6 | import akka.http.scaladsl.Http
7 | import akka.http.scaladsl.model.{HttpResponse, StatusCodes}
8 | import akka.http.scaladsl.server.Directives._
9 | import akka.stream.ActorMaterializer
10 | import util.Utils
11 |
12 | import scala.concurrent.Future
13 |
14 | object FutureWebServer {
15 | implicit val actorSystem = ActorSystem()
16 | implicit val ec = actorSystem.dispatcher
17 | implicit val materializer = ActorMaterializer()
18 |
19 | val routes = (path("endpoint") & get) {
20 | val future = Future {
21 | Utils.doWork
22 | Utils.doWork
23 |
24 | 42
25 | }
26 |
27 | complete {
28 | future
29 | .map(result => HttpResponse(entity = result.toString))
30 | .recover {
31 | case _: NoSuchElementException => HttpResponse(StatusCodes.NotFound)
32 | }
33 | }
34 | }
35 |
36 | def main(args: Array[String]): Unit = {
37 | val serverBinding = Http().bindAndHandle(routes, "0.0.0.0", 9000)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/http/HttpClient.scala:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import concurrent.impl.{Future, Promise}
4 | import org.asynchttpclient.Dsl._
5 | import org.asynchttpclient._
6 |
7 | import scala.util.Try
8 |
9 | object HttpClient {
10 | val client = asyncHttpClient()
11 |
12 | def get(url: String): Future[Response] = {
13 | val p = Promise[Response]
14 |
15 | val response = client.prepareGet(url).setFollowRedirect(true).execute()
16 | response.addListener(() => p.complete(Try(response.get())), null)
17 |
18 | p.future
19 | }
20 |
21 | def getScalaFuture(url: String): scala.concurrent.Future[Response] = {
22 | val p = scala.concurrent.Promise[Response]
23 |
24 | val response = client.prepareGet(url).setFollowRedirect(true).execute()
25 | response.addListener(() => p.complete(Try(response.get())), null)
26 |
27 | p.future
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/http/HttpRequests.scala:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | object HttpRequests extends App {
4 | import concurrent.Executors.defaultExecutor
5 |
6 | val result = for {
7 | (a, b) <- HttpClient.get("https://google.com").zip(HttpClient.get("https://www.scala-lang.org/"))
8 |
9 | aLength = a.getResponseBody.length
10 | bLength = b.getResponseBody.length
11 | sum = aLength + bLength
12 |
13 | randomNumber <- HttpClient.get(s"https://www.random.org/integers/?num=1&min=1&max=$sum&col=1&base=10&format=plain")
14 | } yield randomNumber.getResponseBody
15 |
16 | result.foreach(println)
17 | }
18 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/io/Console.scala:
--------------------------------------------------------------------------------
1 | package io
2 |
3 | import scala.io.StdIn
4 |
5 | object Console {
6 | def putStrLn(line: String): IO[Unit] = IO(() => println(line))
7 | def getStrLn: IO[String] = IO(() => StdIn.readLine())
8 | }
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/io/HelloName.scala:
--------------------------------------------------------------------------------
1 | package io
2 |
3 | object HelloName extends App {
4 | import Console._
5 |
6 | val program = for {
7 | _ <- putStrLn("What is your name?")
8 | name <- getStrLn
9 | _ <- putStrLn("Hello, " + name + ", welcome!")
10 | } yield ()
11 |
12 | program.unsafeRun()
13 | }
14 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/io/IO.scala:
--------------------------------------------------------------------------------
1 | package io
2 |
3 | case class IO[A](unsafeRun: () => A) {
4 | def map[B](f: A => B) = IO(() => f(this.unsafeRun()))
5 | def flatMap[B](f: A => IO[B]): IO[B] =
6 | IO(() => f(this.unsafeRun()).unsafeRun())
7 | }
8 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/tasks/TaskExample.scala:
--------------------------------------------------------------------------------
1 | package tasks
2 |
3 | import http.HttpClient
4 | import monix.eval.Task
5 | import monix.execution.Scheduler.Implicits.global
6 | import org.asynchttpclient.Response
7 |
8 | import scala.concurrent.Future
9 |
10 | object TaskExample extends App {
11 | def randomResult(upTo: Int): Future[Response] =
12 | HttpClient.getScalaFuture(s"https://www.random.org/integers/?num=1&min=1&max=$upTo&col=1&base=10&format=plain")
13 |
14 | val taskA = Task {
15 | println("taskA")
16 | 2 + 3 + 5
17 | }
18 | val taskB = Task.evalOnce {
19 | println("taskB")
20 | 7 + 11 + 13
21 | }
22 |
23 | val taskC = Task.zipMap2(taskA, taskB)(_ + _)
24 |
25 | val result = taskC.flatMap { c =>
26 | Task.deferFuture(randomResult(c))
27 | }
28 |
29 | result.foreach(println)
30 | result.foreach(println)
31 |
32 | Thread.sleep(2000)
33 | }
34 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/threads/ThreadsExample.scala:
--------------------------------------------------------------------------------
1 | package threads
2 |
3 | import util.Utils
4 |
5 | object ThreadsExample extends App {
6 | def doWork = Utils.doWork
7 |
8 | def createThread(work: => Unit) = new Thread(() => work)
9 |
10 | Utils.time("Without threads") {
11 | doWork
12 | doWork
13 | // doWork
14 | // doWork
15 | // doWork
16 | // doWork
17 | // doWork
18 | // doWork
19 | }
20 |
21 | Utils.time("With threads") {
22 | val thread1 = createThread(doWork)
23 | val thread2 = createThread(doWork)
24 | // val thread3 = createThread(doWork)
25 | // val thread4 = createThread(doWork)
26 | // val thread5 = createThread(doWork)
27 | // val thread6 = createThread(doWork)
28 | // val thread7 = createThread(doWork)
29 | // val thread8 = createThread(doWork)
30 |
31 | thread1.start()
32 | thread2.start()
33 | // thread3.start()
34 | // thread4.start()
35 | // thread5.start()
36 | // thread6.start()
37 | // thread7.start()
38 | // thread8.start()
39 |
40 | thread1.join()
41 | thread2.join()
42 | // thread3.join()
43 | // thread4.join()
44 | // thread5.join()
45 | // thread6.join()
46 | // thread7.join()
47 | // thread8.join()
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/threads/ThreadsSharingData.scala:
--------------------------------------------------------------------------------
1 | package threads
2 |
3 | object ThreadsSharingData extends App {
4 | var improveCalculation = true
5 |
6 | val thread = new Thread(new Runnable {
7 | def run(): Unit = {
8 | var i = 0L
9 |
10 | while (improveCalculation) {
11 | i += 1
12 | }
13 |
14 | println(s"Thread exiting: $i")
15 | }
16 | })
17 |
18 | thread.start()
19 |
20 | println("Main going to sleep...")
21 | Thread.sleep(1000)
22 | println("Main waking up...")
23 |
24 | improveCalculation = false
25 |
26 | thread.join()
27 |
28 | println("Main exiting")
29 | }
30 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/threads/ThreadsSharingData2.scala:
--------------------------------------------------------------------------------
1 | package threads
2 |
3 | class MutableData(var name: String, var shouldContinue: Boolean)
4 |
5 | object ThreadsSharingData2 extends App {
6 | var improveCalculation = true
7 | var sharedData = new MutableData("Test", true)
8 |
9 | val thread = new Thread(new Runnable {
10 | def run(): Unit = {
11 | var i = 0L
12 |
13 | while (improveCalculation && sharedData.shouldContinue) {
14 | i += 1
15 | }
16 |
17 | println(s"Thread exiting: $i")
18 | println(s"Name: ${sharedData.name}")
19 | }
20 | })
21 |
22 | thread.start()
23 |
24 | println("Main going to sleep...")
25 | Thread.sleep(1000)
26 | println("Main waking up...")
27 |
28 | improveCalculation = false
29 | sharedData.name = "Test finished"
30 | sharedData.shouldContinue = false
31 |
32 | thread.join()
33 |
34 | println("Main exiting")
35 | }
36 |
--------------------------------------------------------------------------------
/lectures/examples/10-concurrency/src/main/scala/util/Utils.scala:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | object Utils {
4 | def time[T](name: String)(operation: => T): T = {
5 | val startTime = System.currentTimeMillis()
6 |
7 | val result = operation
8 |
9 | println(s"$name took ${System.currentTimeMillis - startTime} millis")
10 |
11 | result
12 | }
13 |
14 | def doWork = (1 to 2000000).map(math.pow(2, _)).toList
15 | }
16 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/build.sbt:
--------------------------------------------------------------------------------
1 | name := "type-classes"
2 | version := "0.1"
3 |
4 | libraryDependencies ++= Seq(
5 | "org.typelevel" %% "cats-core" % "1.6.0",
6 | "org.typelevel" %% "spire" % "0.14.1",
7 | "org.scalatest" %% "scalatest" % "3.0.5" % "test"
8 | )
9 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.6
2 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/cats/CatsMonoidDemo.scala:
--------------------------------------------------------------------------------
1 | package cats
2 |
3 | import cats.implicits._
4 | import math.Rational
5 |
6 | object CatsMonoidDemo extends App {
7 | implicit val rationalMonoid = new Monoid[Rational] {
8 | def empty: Rational = 0
9 | def combine(x: Rational, y: Rational): Rational = x + y
10 | }
11 |
12 | (2, 3) |+| (4, 5) // (6, 8)
13 |
14 | val map1 = Map(1 -> (2, Rational(3, 2)), 2 -> (3, Rational(4)))
15 | val map2 = Map(2 -> (5, Rational(6)), 3 -> (7, Rational(8, 3)))
16 |
17 | println(map1 |+| map2)
18 | }
19 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/cats/EqDemo.scala:
--------------------------------------------------------------------------------
1 | package cats
2 |
3 | import math.Rational
4 | import cats.implicits._
5 |
6 | object EqDemo extends App {
7 | // 2 === ""
8 |
9 | implicit val rationalEq = new Eq[Rational] {
10 | def eqv(x: Rational, y: Rational): Boolean = x == y
11 | }
12 |
13 | println(Rational(5) === Rational(10, 2))
14 | println(Rational(5, 2) =!= Rational(10, 2))
15 | // println(Rational(5, 2) === "")
16 | println(Rational(5, 2) === 2)
17 |
18 | case class Box[+A](a: A) {
19 | def contains[B >: A : Eq](b: B) = b === a
20 | }
21 |
22 | Box(1).contains(1)
23 | // Box(1).contains("")
24 |
25 | List(1).contains("")
26 | }
27 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/json/Json.scala:
--------------------------------------------------------------------------------
1 | package json
2 |
3 | import scala.language.implicitConversions
4 |
5 | sealed trait JsonValue
6 | case class JsonNumber(value: BigDecimal) extends JsonValue
7 | case class JsonString(value: String) extends JsonValue
8 | case class JsonBoolean(value: Boolean) extends JsonValue
9 | case class JsonArray(value: Seq[JsonValue]) extends JsonValue
10 | case class JsonObject(value: Map[String, JsonValue]) extends JsonValue
11 | case object JsonNull extends JsonValue
12 |
13 | object JsonValue {
14 | def toString(json: JsonValue): String = json match {
15 | case JsonNumber(value) => value.toString
16 | case JsonString(value) => s"""\"$value\""""
17 | case JsonBoolean(value) => value.toString
18 | case JsonArray(elements) => "[" + elements.map(toString).mkString(", ") + "]"
19 | case JsonObject(members) =>
20 | val membersStrings = members.map { case (key, value) =>
21 | s""" \"$key\": ${toString(value)}"""
22 | }
23 | "{\n" + membersStrings.mkString(",\n") + "\n}"
24 | case JsonNull => "null"
25 | }
26 | }
27 |
28 | trait JsonSerializable[A] {
29 | def toJson(a: A): JsonValue
30 | }
31 |
32 | object JsonSerializable {
33 | def toJson[A](a: A)(implicit js: JsonSerializable[A]): JsonValue = js.toJson(a)
34 | def toString[A : JsonSerializable](a: A) = JsonValue.toString(toJson(a))
35 |
36 | object ops {
37 | implicit class JsonSerializableOps[A : JsonSerializable](a: A) {
38 | def toJson = JsonSerializable.toJson(a)
39 | def toJsonString = JsonSerializable.toString(a)
40 | }
41 | }
42 |
43 | implicit val intSerializable = new JsonSerializable[Int] {
44 | def toJson(a: Int): JsonValue = JsonNumber(a)
45 | }
46 |
47 | implicit val stringSerializable = new JsonSerializable[String] {
48 | def toJson(a: String): JsonValue = JsonString(a)
49 | }
50 |
51 | implicit val booleanSerializable = new JsonSerializable[Boolean] {
52 | def toJson(a: Boolean): JsonValue = JsonBoolean(a)
53 | }
54 |
55 | implicit def listSerializable[A : JsonSerializable] = new JsonSerializable[List[A]] {
56 | def toJson(a: List[A]): JsonValue = JsonArray(
57 | a.map(value => JsonSerializable.toJson(value))
58 | )
59 | }
60 |
61 | implicit def optionSerializable[A : JsonSerializable] = new JsonSerializable[Option[A]] {
62 | def toJson(opt: Option[A]): JsonValue = opt match {
63 | case Some(a) => JsonSerializable.toJson(a)
64 | case _ => JsonNull
65 | }
66 | }
67 |
68 | // Utilities for easier creation of object serializables
69 | // We will use a simple trick with a wrapper
70 | case class JsonValueWrapper(value: JsonValue)
71 |
72 | implicit def toJsonValueWrapper[A : JsonSerializable](value: A): JsonValueWrapper =
73 | JsonValueWrapper(JsonSerializable.toJson(value))
74 |
75 | def jsonObj(fields: (String, JsonValueWrapper)*): JsonObject = JsonObject {
76 | fields.toMap.mapValues(_.value)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/json/JsonDemo.scala:
--------------------------------------------------------------------------------
1 | package json
2 |
3 | import json.JsonSerializable.jsonObj
4 | import json.JsonSerializable.ops._
5 |
6 | object JsonDemo extends App {
7 | List(1, 2, 3).toJsonString // [1, 2, 3]
8 |
9 | val ivan = Person("Ivan", "ivan@abv.bg", 23)
10 | val georgi = Person("Georgi", "georgi@gmail.bg", 28)
11 |
12 | ivan.toJson // JsonObject(Map(name -> JsonString(Ivan), email -> JsonString(ivan@abv.bg), age -> JsonNumber(23)))
13 | ivan.toJsonString // {"name": "Ivan", "email": "ivan@abv.bg", "age": 23}
14 |
15 | // Person's JsonSerializable composes with List's JsonSerializable
16 | List(ivan, georgi).toJsonString // [{"name": "Ivan", "email": "ivan@abv.bg", "age": 23}, {"name": "Georgi", "email": "georgi@abv.bg", "age": 28}]
17 |
18 | {
19 | // We might want to skip some fields, like email, for example because we don't want to share
20 | // user's email with other users.
21 | // It's really easy to do so with this implementation -
22 | // just provide another Person's JsonSerializable in this context
23 |
24 | implicit val personSerializable = new JsonSerializable[Person] {
25 | // Using the jsonObj utility
26 | def toJson(person: Person): JsonValue = jsonObj(
27 | "name" -> person.name,
28 | "age" -> person.age
29 | )
30 | }
31 |
32 | // It will compose just as easily
33 | println {
34 | List(ivan, georgi).toJsonString // [{"name": "Ivan", "age": 23}, {"name": "Georgi", "age": 28}]
35 | }
36 | }
37 |
38 | // We can implement this for deserialization, too. play-json is an example for library that implements this pattern
39 | // and provides many utilities to ease the creation of (de)serializers.
40 | // Since the mapping depends on only compile-time information the implementation is faster than e.g. Jackson (which uses reflection)
41 | }
42 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/json/Person.scala:
--------------------------------------------------------------------------------
1 | package json
2 |
3 | case class Person(name: String, email: String, age: Int)
4 |
5 | object Person {
6 | implicit val personSerializable = new JsonSerializable[Person] {
7 | def toJson(person: Person): JsonValue = JsonObject(Map(
8 | "name" -> JsonString(person.name),
9 | "email" -> JsonString(person.email),
10 | "age" -> JsonNumber(person.age)
11 | ))
12 | }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/lasttime/FutureDemo.scala:
--------------------------------------------------------------------------------
1 | package lasttime
2 |
3 | import scala.concurrent.{Await, Future}
4 | import scala.concurrent.ExecutionContext.Implicits._
5 | import scala.concurrent.duration._
6 |
7 | object FutureDemo extends App {
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 | }
26 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/math/ListOrderingDemo.scala:
--------------------------------------------------------------------------------
1 | package math
2 |
3 | object ListOrderingDemo extends App {
4 | implicit def listOrdering[A : Ordering]: Ordering[List[A]] = new Ordering[List[A]] {
5 | def compare(x: List[A], y: List[A]): Int = {
6 | val aOrdering = Ordering[A]
7 |
8 | if (x == y) 0
9 | else if (x.isEmpty) -1
10 | else if (y.isEmpty) 1
11 | else if (x.isEmpty || aOrdering.lt(x.head, y.head)) -1
12 | else if (y.isEmpty || aOrdering.gt(x.head, y.head)) -1
13 | else compare(x.tail, y.tail)
14 | }
15 | }
16 |
17 | val sortedList = List(
18 | List(1, 2, 3),
19 | List(3, 4),
20 | List.empty[Int],
21 | List(1, 1)
22 | ).sorted
23 |
24 | println(sortedList)
25 | }
26 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/math/Monoid.scala:
--------------------------------------------------------------------------------
1 | package math
2 |
3 | trait Monoid[M] extends Semigroup[M] {
4 | def op(a: M, b: M): M
5 | def identity: M
6 | }
7 |
8 | object Monoid {
9 | def apply[A](implicit m: Monoid[A]): Monoid[A] = m
10 |
11 | object ops {
12 | implicit class MonoidOps[A](val a: A) extends AnyVal {
13 | def |+|(b: A)(implicit m: Monoid[A]) = m.op(a, b)
14 | }
15 | }
16 |
17 | implicit val intAdditiveMonoid = new Monoid[Int] {
18 | def op(a: Int, b: Int): Int = a + b
19 | val identity: Int = 0
20 | }
21 |
22 | val intMultiplicativeMonoid = new Monoid[Int] {
23 | def op(a: Int, b: Int): Int = a * b
24 | val identity: Int = 1
25 | }
26 |
27 | implicit val stringMonoid = new Monoid[String] {
28 | def op(a: String, b: String): String = a + b
29 | val identity: String = ""
30 | }
31 |
32 | implicit def optionMonoid[A : Monoid] = new Monoid[Option[A]] {
33 | import ops._
34 |
35 | def op(a: Option[A], b: Option[A]): Option[A] = (a, b) match {
36 | case (Some(n), Some(m)) => Some(n |+| m)
37 | case (Some(_), _) => a
38 | case (_, Some(_)) => b
39 | case _ => None
40 | }
41 |
42 | def identity: Option[A] = None
43 | }
44 |
45 | implicit def pairMonoid[A : Monoid, B : Monoid] = new Monoid[(A, B)] {
46 | import ops._
47 |
48 | def op(a: (A, B), b: (A, B)): (A, B) = (a, b) match {
49 | case ((a1, a2), (b1, b2)) => (a1 |+| b1, a2 |+| b2)
50 | }
51 |
52 | def identity: (A, B) = (Monoid[A].identity, Monoid[B].identity)
53 | }
54 |
55 | implicit def mapMonoid[K, V : Monoid] = new Monoid[Map[K, V]] {
56 | import ops._
57 |
58 | def op(a: Map[K, V], b: Map[K, V]): Map[K, V] = {
59 | val vIdentity = Monoid[V].identity
60 |
61 | (a.keySet ++ b.keySet).foldLeft(identity) { (acc, key) =>
62 | acc + (key -> (a.getOrElse(key, vIdentity) |+| b.getOrElse(key, vIdentity)))
63 | }
64 | }
65 |
66 | def identity: Map[K, V] = Map.empty[K, V]
67 | }
68 | }
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/math/MonoidDemo.scala:
--------------------------------------------------------------------------------
1 | package math
2 |
3 | import Monoid.ops._
4 |
5 | object MonoidDemo extends App {
6 | def sum[A : Monoid](xs: List[A]) = {
7 | xs.foldLeft(Monoid[A].identity)(_ |+| _)
8 | }
9 |
10 | sum(List(Rational(3, 4), Rational(5), Rational(7, 4), Rational(11, 13))) // 217/26
11 | sum(List(Rational(1, 2), Rational(4))) // 6/8
12 | sum(List(1, 2, 3, 4, 5)) // 15
13 |
14 | {
15 | // We can explicitly change the context (with a new implicit val or with an import of an implicit val)
16 |
17 | implicit val ratMonoid = Rational.rationalMultiplicativeMonoid
18 | implicit val intMonoid = Monoid.intMultiplicativeMonoid
19 |
20 | sum(List(Rational(3, 4), Rational(5), Rational(7, 4), Rational(11, 13))) // 1155/208
21 | sum(List(Rational(1, 2), Rational(4))) // 2/1
22 | sum(List(1, 2, 3, 4, 5)) // 120
23 | }
24 |
25 |
26 | // Composite monoids:
27 |
28 | // Option:
29 |
30 | sum(List(
31 | Some(Rational(1)),
32 | None,
33 | Some(Rational(1, 2)),
34 | Some(Rational(3, 8)),
35 | None
36 | )) // Some(15/8)
37 |
38 | // Pairs
39 | (2, 3) |+| (4, 5) // (6, 8)
40 |
41 | val map1 = Map(1 -> (2, Rational(3, 2)), 2 -> (3, Rational(4)))
42 | val map2 = Map(2 -> (5, Rational(6)), 3 -> (7, Rational(8, 3)))
43 |
44 | // Composes Monoid[Int] and Monoid[Rational] into a pair monoid Monoid[(Int, Rational)]
45 | // and then composes the pair monoid into Monoid[Map[Int, (Int, Rational)]
46 | println(map1 |+| map2) // Map(1 -> (2,3/2), 2 -> (8,10/1), 3 -> (7,8/3))
47 | }
48 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/math/NumericTypeclassDemo.scala:
--------------------------------------------------------------------------------
1 | package math
2 |
3 | object NumericTypeclassDemo extends App {
4 | // List("a", "b", "cd").sum // does not compile: could not find implicit value for parameter num: Numeric[String]
5 |
6 | List(1, 2, 3, 4).sum // 10
7 |
8 | // implicit val rationalNumeric = new Numeric[Rational] {
9 | // def plus(x: Rational, y: Rational): Rational = x + y
10 | // def minus(x: Rational, y: Rational): Rational = x - y
11 | // def times(x: Rational, y: Rational): Rational = x * y
12 | // def negate(x: Rational): Rational = -x
13 | //
14 | // def fromInt(x: Int): Rational = x
15 | // def toInt(x: Rational): Int = x.numer / x.denom
16 | // def toLong(x: Rational): Long = x.numer / x.denom
17 | // def toFloat(x: Rational): Float = x.numer.toFloat / x.denom
18 | // def toDouble(x: Rational): Double = x.numer.toDouble / x.denom
19 | //
20 | // def compare(x: Rational, y: Rational): Int = x compare y
21 | // }
22 | //
23 | // List(Rational(3, 4), Rational(1, 2), Rational(2, 5)).sum // 33/20
24 | // List(Rational(3, 4), Rational(1, 2), Rational(2, 5)).product // 3/20
25 | }
26 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/math/OrderingTypeClassDemo.scala:
--------------------------------------------------------------------------------
1 | package math
2 |
3 | import scala.math.abs
4 |
5 | object OrderingTypeClassDemo extends App {
6 | def quickSort[T](xs: List[T])(implicit m: Ordering[T]): List[T] = {
7 | import m.mkOrderingOps
8 |
9 | xs match {
10 | case Nil => Nil
11 | case x :: rest =>
12 | val (before, after) = rest partition { _ < x }
13 | quickSort(before) ++ (x :: quickSort(after))
14 | }
15 | }
16 |
17 | implicit val intOrdering = Ordering[Int].reverse
18 |
19 | quickSort(List(5, 1, 2, 3)) // List(5, 3, 2, 1)
20 | quickSort(List(-5, 1, 2, -2, 3)) // List(3, 2, 1, -2, -5)
21 | quickSort(List(-5, 1, 2, -2, 3))(Ordering.by(abs)) // List(-5, 3, 2, -2, 1)
22 | }
23 |
--------------------------------------------------------------------------------
/lectures/examples/11-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.signum * 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 |
51 | val identity: Rational = 0
52 | }
53 |
54 | val rationalMultiplicativeMonoid = new Monoid[Rational] {
55 | def op(a: Rational, b: Rational): Rational = a * b
56 |
57 | val identity: Rational = 1
58 | }
59 | }
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/math/Semigroup.scala:
--------------------------------------------------------------------------------
1 | package math
2 |
3 | trait Semigroup[M] {
4 | def op(a: M, b: M): M
5 | }
6 |
7 | object Semigroup {
8 | def apply[A](implicit m: Semigroup[A]): Semigroup[A] = m
9 |
10 | object ops {
11 | implicit class SemigroupOps[A](val a: A) extends AnyVal {
12 | def |+|(b: A)(implicit m: Semigroup[A]) = m.op(a, b)
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/parallel/ParallelCollections.scala:
--------------------------------------------------------------------------------
1 | package parallel
2 |
3 | import math.Monoid
4 | import math.Monoid.ops._
5 | import parallel.Utils.time
6 |
7 | import scala.collection.GenSeq
8 |
9 | object ParallelCollections extends App {
10 | def sum[A : Monoid](xs: GenSeq[A]): A = {
11 | xs.fold(Monoid[A].identity)(_ |+| _)
12 | // xs.foldLeft(Monoid[A].identity)(_ |+| _)
13 | }
14 |
15 | val seq = 1 to 900000000
16 |
17 | // // Non-associative operation
18 | // implicit val badMonoid = new Monoid[Int] {
19 | // def op(a: Int, b: Int): Int = a - b
20 | // def identity: Int = 0
21 | // }
22 |
23 | println(time("Single threaded")(sum(seq)))
24 | println(time("Multi threaded")(sum(seq.par)))
25 | }
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 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/spire/VectorSpaceDemo.scala:
--------------------------------------------------------------------------------
1 | package spire
2 |
3 | import spire.algebra.Field
4 | import spire.algebra.VectorSpace
5 | import spire.implicits._
6 |
7 | object VectorSpaceDemo extends App {
8 | val vectorSpaceExpr = 5 *: Vector(1, 2, 3) + (-1) *: Vector(3, 4, -5)
9 |
10 | println(vectorSpaceExpr)
11 |
12 | // implicit def fnSpace[A] = new VectorSpace[A => Double, Double] {
13 | // implicit def scalar: Field[Double] = ???
14 | //
15 | // def zero: A => Double = ???
16 | // def negate(x: A => Double): A => Double = ???
17 | // def plus(x: A => Double, y: A => Double): A => Double = ???
18 | // def timesl(r: Double, v: A => Double): A => Double = ???
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 | // composed(10)
27 | }
28 |
--------------------------------------------------------------------------------
/lectures/examples/11-type-classes/src/main/scala/types/ClassMetainfoContextDemo.scala:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import math.Rational
4 |
5 | import scala.reflect.ClassTag
6 | import scala.reflect.runtime.universe._
7 |
8 | object ClassMetainfoContextDemo extends App {
9 | // Does not compile because creation of Java arrays requires metainformation
10 | // (they are different for primitives and the different kind of objects)
11 | // def arrayOf[A](seq: A*) = Array[A](seq: _*)
12 |
13 | // ClassTag gives us the information we need for creating arrays
14 | def arrayOf[A : ClassTag](seq: A*) = Array[A](seq: _*)
15 |
16 | arrayOf(Rational(1), Rational(2))
17 |
18 | // On JVM no generic type info is available at runtime.
19 | // Scala allows accessing such info through a couple of metainfo type classes like TypeTag,
20 | // which are automatically created for every concrete type
21 | def listElementType[A : TypeTag](xs: List[A]) = {
22 | val t = typeOf[A]
23 |
24 | if (t =:= typeOf[Int]) "List of ints"
25 | else if(t =:= typeOf[Rational]) "List of rationals"
26 | else if (t =:= typeOf[List[String]]) "List of list of strings"
27 | else "List of something else"
28 | }
29 |
30 | val results = List(
31 | listElementType(List(Rational(2), Rational(3))), // List of rationals
32 | listElementType(List(List("a", "b"))), // List of rationals
33 | listElementType(List(List(1, 2))) // List of rationals
34 | )
35 |
36 | println(results)
37 | }
38 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/lecture-code/build.sbt:
--------------------------------------------------------------------------------
1 | name := "monads-and-functors"
2 | version := "0.1"
3 |
4 | libraryDependencies ++= Seq(
5 | "org.typelevel" %% "cats-core" % "1.6.0",
6 | "org.scalatest" %% "scalatest" % "3.0.5" % "test"
7 | )
8 |
9 | scalacOptions += "-Ypartial-unification"
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/lecture-code/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.6
2 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/lecture-code/src/main/scala/concurrent/FailSafeExample.scala:
--------------------------------------------------------------------------------
1 | package concurrent
2 |
3 | import scala.concurrent.ExecutionContext.Implicits.global
4 | import scala.concurrent.{Await, Future}
5 | import scala.concurrent.duration._
6 |
7 | object FailSafeExample extends App {
8 | def double(n: Int, shouldFail: Boolean = false): Future[Int] = Future {
9 | println(s"Executing double with $n")
10 |
11 | if (shouldFail) throw new RuntimeException
12 |
13 | n * 2
14 | }
15 |
16 | val execution = for {
17 | a <- double(2)
18 | b <- double(a, true)
19 | c <- double(b)
20 | } yield c
21 |
22 | val additionOperation = execution.map(_ + 10)
23 |
24 | val result = Await.result(additionOperation, 2.seconds)
25 |
26 | println(result)
27 | }
28 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/lecture-code/src/main/scala/effects/Functor.scala:
--------------------------------------------------------------------------------
1 | package effects
2 |
3 | import scala.language.higherKinds
4 |
5 | trait Functor[F[_]] {
6 | def map[A, B](fa: F[A])(f: A => B): F[B]
7 | }
8 |
9 | object Functor {
10 | def apply[F[_]](implicit f: Functor[F]) = f
11 |
12 | trait FunctorOps {
13 | implicit class functorOps[F[_] : Functor, A](fa: F[A]) {
14 | def map[B](f: A => B): F[B] = Functor[F].map(fa)(f)
15 | }
16 | }
17 | object ops extends FunctorOps
18 | }
19 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/lecture-code/src/main/scala/effects/Monad.scala:
--------------------------------------------------------------------------------
1 | package effects
2 |
3 | import effects.Monad.MonadOps
4 |
5 | import scala.concurrent.{ExecutionContext, Future}
6 | import scala.language.higherKinds
7 | import scala.util.{Failure, Success, Try}
8 |
9 | trait Monad[F[_]] extends Functor[F] {
10 | def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
11 | def unit[A](a: => A): F[A]
12 |
13 | def compose[A, B, C](f: A => F[B], g: B => F[C]): A => F[C] = a => flatMap(f(a))(g)
14 |
15 | def map[A, B](m: F[A])(f: A => B): F[B] = flatMap(m)(a => unit(f(a)))
16 | def flatten[A](mm: F[F[A]]): F[A] = flatMap(mm)(x => x)
17 |
18 | def zip[A, B](fa: F[A], fb: F[B]): F[(A, B)] = flatMap(fa)(a => map(fb)(b => (a, b)))
19 | def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = map(zip(fa, fb)) { case (a, b) => f(a, b) }
20 |
21 | def sequence[A](xs: List[F[A]]): F[List[A]] = {
22 | xs.foldRight(unit(List[A]())) { (next, acc) =>
23 | map2(next, acc)(_ :: _)
24 | }
25 | }
26 | }
27 |
28 | object Monad {
29 | def apply[F[_]](implicit m: Monad[F]) = m
30 |
31 | def map[A, B, F[_]](ma: F[A])(f: A => B)(implicit m: Monad[F]) = m.map(ma)(f)
32 | def flatMap[A, B, F[_]](ma: F[A])(f: A => F[B])(implicit m: Monad[F]) = m.flatMap(ma)(f)
33 |
34 | def compose[A, B, C, F[_]](f: A => F[B], g: B => F[C])(implicit m: Monad[F]) = m.compose(f, g)
35 | def zip[A, B, F[_]](ma: F[A], mb: F[B])(implicit m: Monad[F]) = m.zip(ma, mb)
36 | def map2[A, B, C, F[_]](ma: F[A], mb: F[B])(f: (A, B) => C)(implicit m: Monad[F]) = m.map2(ma, mb)(f)
37 | def sequence[A, F[_]](ml: List[F[A]])(implicit m: Monad[F]) = m.sequence(ml)
38 |
39 | trait MonadOps {
40 | implicit class MonadOps[F[_] : Monad, A](fa: F[A]) {
41 | def map[B](f: A => B) = Monad[F].map(fa)(f)
42 | def flatMap[B](f: A => F[B]) = Monad[F].flatMap(fa)(f)
43 | def flatten[B](implicit ev: A =:= F[B]): F[B] = {
44 | val ffb = Monad[F].map(fa)(ev)
45 | Monad[F].flatten(ffb)
46 | }
47 | }
48 |
49 | implicit class ListWithMonadOps[F[_] : Monad, A](faxs: List[F[A]]) {
50 | def sequence = Monad.sequence(faxs)
51 | }
52 | }
53 | object ops extends MonadOps
54 |
55 | // Implementing monads from the Scala library
56 | implicit val optionMonad = new MonadFilter[Option] {
57 | def flatMap[A, B](m: Option[A])(f: A => Option[B]): Option[B] = m flatMap f
58 | def unit[A](a: => A): Option[A] = Some(a)
59 | def mzero[A]: Option[A] = None
60 |
61 | override def map[A, B](m: Option[A])(f: A => B): Option[B] = m map f
62 | }
63 |
64 | implicit val tryMonad = new MonadFilter[Try] {
65 | def flatMap[A, B](m: Try[A])(f: A => Try[B]): Try[B] = m flatMap f
66 | def unit[A](a: => A): Try[A] = Success(a)
67 | def mzero[A]: Try[A] = Failure(new NoSuchElementException)
68 |
69 | override def map[A, B](m: Try[A])(f: A => B): Try[B] = m map f
70 | }
71 |
72 | implicit val listMonad = new MonadFilter[List] {
73 | def flatMap[A, B](m: List[A])(f: A => List[B]): List[B] = m flatMap f
74 | def unit[A](a: => A): List[A] = List(a)
75 | def mzero[A]: List[A] = Nil
76 |
77 | override def map[A, B](m: List[A])(f: A => B): List[B] = m map f
78 | }
79 |
80 | implicit def futureMonad(implicit ec: ExecutionContext) = new MonadFilter[Future] {
81 | def flatMap[A, B](m: Future[A])(f: A => Future[B]): Future[B] = m flatMap f
82 | def unit[A](a: => A): Future[A] = Future(a)
83 | def mzero[A]: Future[A] = Future.failed(new NoSuchElementException)
84 |
85 | override def map[A, B](m: Future[A])(f: A => B): Future[B] = m map f
86 | }
87 |
88 | implicit def eitherMonad[E] = new Monad[({type T[A] = Either[E, A]})#T] {
89 | def flatMap[A, B](fa: Either[E, A])(f: A => Either[E, B]): Either[E, B] = fa.flatMap(f)
90 | def unit[A](a: => A): Either[E, A] = Right(a)
91 | }
92 | }
93 |
94 | trait MonadFilter[F[_]] extends Monad[F] {
95 | def mzero[A]: F[A]
96 | def filter[A](fa: F[A])(f: A => Boolean): F[A] = flatMap(fa) { x => if (f(x)) unit(x) else mzero }
97 | }
98 |
99 | object MonadFilter {
100 | def apply[F[_]](implicit m: MonadFilter[F]) = m
101 |
102 | trait MonadFilterOps extends MonadOps {
103 | implicit class MonadFilterOps[F[_] : MonadFilter, A](fa: F[A]) {
104 | def filter(f: A => Boolean) = MonadFilter[F].filter(fa)(f)
105 | }
106 | }
107 | object ops extends MonadFilterOps
108 | }
109 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/lecture-code/src/main/scala/effects/cats/FunctorCompositionDemo.scala:
--------------------------------------------------------------------------------
1 | package effects.cats
2 |
3 | import cats.Functor
4 | import cats.data.Nested
5 | import cats.implicits._
6 |
7 | object FunctorCompositionDemo extends App {
8 | val listOfOptions = List(Some(1), None, Some(2))
9 |
10 | println {
11 | // composition
12 | Functor[List].compose[Option].map(listOfOptions)(_ + 1)
13 | }
14 |
15 | // // Cats utilities:
16 | // println {
17 | // Nested(listOfOptions).map(_ + 1)
18 | // }
19 | }
20 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/lecture-code/src/main/scala/effects/maybe/Maybe.scala:
--------------------------------------------------------------------------------
1 | package effects.maybe
2 |
3 | import effects.Monad
4 | import Monad.ops._
5 |
6 | trait Maybe[+A]
7 | case class Just[+A](a: A) extends Maybe[A]
8 | case object Nthng extends Maybe[Nothing]
9 |
10 | object Maybe {
11 | implicit val maybeMonad = new Monad[Maybe] {
12 | def flatMap[A, B](fa: Maybe[A])(f: A => Maybe[B]): Maybe[B] = fa match {
13 | case Just(a) => f(a)
14 | case _ => Nthng
15 | }
16 |
17 | def unit[A](a: =>A): Maybe[A] = Just(a)
18 | }
19 | }
20 |
21 | object MaybeDemo extends App {
22 | def f(n: Int): Maybe[Int] = Just(n + 1)
23 | def g(n: Int): Maybe[Int] = Just(n * 2)
24 | def h(n: Int): Maybe[Int] = Just(n * n)
25 |
26 | val a = 3
27 |
28 | val result = for {
29 | b <- f(a)
30 | c <- g(b * 2)
31 | d <- h(b + c)
32 | } yield a * b * d
33 | println(result)
34 |
35 |
36 |
37 | val maybe1: Maybe[Int] = Just(42)
38 | val maybe2: Maybe[Int] = Just(10)
39 |
40 | Monad.map2(maybe1, maybe2)(_ + _)
41 |
42 | val listOfMaybes = List(Just(1), Nthng, Just(2))
43 |
44 | println(
45 | Monad.sequence(listOfMaybes),
46 | listOfMaybes.sequence
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/lecture-code/src/main/scala/effects/state/RNG.scala:
--------------------------------------------------------------------------------
1 | package effects.state
2 |
3 | import effects.Monad.ops._
4 |
5 | case class RNG(seed: Long) {
6 | def nextInt: (RNG, Int) = {
7 | val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
8 | val nextRNG = RNG(newSeed)
9 | val n = (newSeed >>> 16).toInt
10 |
11 | (nextRNG, n)
12 | }
13 | }
14 |
15 | object RNG {
16 | val nextInt = State((rng: RNG) => rng.nextInt)
17 | val nextBoolean = nextInt.map(_ > 0)
18 | }
19 |
20 | object RNGDemo extends App {
21 | import RNG._
22 |
23 | val randomTuple = for {
24 | a <- nextInt
25 | b <- nextInt
26 | c <- nextBoolean
27 | } yield (a, b, a + b, c)
28 |
29 | println(randomTuple.run(RNG(System.currentTimeMillis)))
30 |
31 |
32 | // Without State we have to do this:
33 | val (rng1, next1) = RNG(System.currentTimeMillis).nextInt
34 | val (rng2, next2) = rng1.nextInt
35 | val (rng3, next3) = rng2.nextInt
36 |
37 | println(next1, next2, next3)
38 | }
39 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/lecture-code/src/main/scala/effects/state/State.scala:
--------------------------------------------------------------------------------
1 | package effects.state
2 |
3 | import effects.Monad
4 |
5 | case class State[S, A](run: S => (S, A))
6 |
7 | object State {
8 | implicit def stateMonad[S] = new Monad[({type T[A] = State[S, A]})#T] {
9 | def flatMap[A, B](fa: State[S, A])(f: A => State[S, B]): State[S, B] = State { s1 =>
10 | val (s2, a) = fa.run(s1)
11 | f(a).run(s2)
12 | }
13 |
14 | def unit[A](a: => A): State[S, A] = State(s => (s, a))
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/build.sbt:
--------------------------------------------------------------------------------
1 | name := "MonadExamples"
2 |
3 | version := "0.1"
4 |
5 | scalaVersion := "2.12.8"
6 |
7 | initialCommands in console := """import example._, MonadInstances._;import scala.concurrent.Future;import scala.concurrent.ExecutionContext.Implicits.global;"""
8 |
9 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version = 1.2.8
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/src/main/scala/example/BasicExample.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | trait BasicExample {
4 |
5 | def sqrt(x: Double): Double = librarySqrt(x)
6 |
7 | def getRoots(a: Double, b: Double, c: Double): (Double, Double) = {
8 | val D = discriminant(a, b, c)
9 | val d = sqrt(D)
10 | val x1 = (-b + d) / (2 * a)
11 | val x2 = (-b - d) / (2 * a)
12 | (x1, x2)
13 | }
14 |
15 | def addSquareRoots(x1: Double, x2: Double): Double = sqrt(x1) + sqrt(x2)
16 |
17 | def getSumOfSquareRoots(a: Double, b: Double, c: Double): Double = {
18 | val roots = getRoots(a, b, c)
19 | addSquareRoots(roots._1, roots._2)
20 | }
21 |
22 | def printSumOfSquarRoots(a: Double, b: Double, c: Double): Unit =
23 | println(s"Result: ${getSumOfSquareRoots(a, b, c)}")
24 | }
25 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/src/main/scala/example/EitherExample.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import MonadInstances._
4 |
5 | trait EitherExample {
6 | type EitherMonad[+A] = Either[String, A]
7 |
8 | def sqrt(x: Double): Either[String, Double] =
9 | if (x >= 0) Right(librarySqrt(x)) else Left(squareRootOfNegativeNumberError(x))
10 |
11 | def validateLeadingCoefficient(lc: Double): Either[String, Double] =
12 | if (lc != 0) Right(lc) else Left(leadingCoefficientError)
13 |
14 | def getRoots(a: Double, b: Double, c: Double): Either[String, (Double, Double)] = {
15 | val D = discriminant(a, b, c)
16 | for {
17 | lc <- validateLeadingCoefficient(a)
18 | d <- sqrt(D).left.map(_ => negativeDiscriminantError(D))
19 | } yield rootHelper(lc, b, d)
20 | }
21 |
22 | def addSquareRoots(x1: Double, x2: Double): Either[String, Double] = {
23 | Monad[EitherMonad].map2(
24 | sqrt(x1).left.map(_ => negativeRootError(x1)),
25 | sqrt(x2).left.map(_ => negativeRootError(x2))
26 | )(_ + _)
27 | }
28 |
29 | def getSumOfSquareRoots(a: Double, b: Double, c: Double): Either[String, Double] =
30 | for {
31 | roots <- getRoots(a, b, c)
32 | sum <- addSquareRoots(roots._1, roots._2)
33 | } yield sum
34 |
35 | def printSumOfSquareRoots(a: Double, b: Double, c: Double): Unit =
36 | getSumOfSquareRoots(a, b, c) match {
37 | case Right(sum) => println(s"Result: $sum")
38 | case Left(errorMessage) => println(errorMessage)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/src/main/scala/example/EitherPractice.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import MonadInstances._
4 |
5 | trait EitherPractice {
6 | type EitherMonad[+A] = Either[String, A]
7 |
8 | def sqrt(x: Double): Either[String, Double]
9 |
10 | def validateLeadingCoefficient(lc: Double): Either[String, Double]
11 |
12 | def getRoots(a: Double, b: Double, c: Double): Either[String, (Double, Double)]
13 |
14 | def addSquareRoots(x1: Double, x2: Double): Either[String, Double]
15 |
16 | def getSumOfSquareRoots(a: Double, b: Double, c: Double): Either[String, Double]
17 |
18 | def printSumOfSquareRoots(a: Double, b: Double, c: Double): Unit =
19 | getSumOfSquareRoots(a, b, c) match {
20 | case Right(sum) => println(s"Result: $sum")
21 | case Left(errorMessage) => println(errorMessage)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/src/main/scala/example/FutureEitherExample.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import example.MonadInstances._
4 |
5 | import scala.concurrent.ExecutionContext.Implicits.global
6 | import scala.concurrent.Future
7 | import scala.util.{Failure, Success}
8 |
9 | trait FutureEitherExample {
10 | type EitherTMonad[+A] = EitherT[Future, String, A]
11 |
12 | def sqrt(x: Double): Either[String, Double] =
13 | if (x >= 0) Right(librarySqrt(x)) else Left(squareRootOfNegativeNumberError(x))
14 |
15 | def deepSqrt(x: Double): Future[Either[String, Double]] = Future {
16 | val time = 2000L
17 | Thread.sleep(time)
18 | println("Thinking...")
19 | sqrt(x)
20 | }
21 |
22 | def validateLeadingCoefficient(lc: Double): Either[String, Double] =
23 | if (lc != 0) Right(lc) else Left(leadingCoefficientError)
24 |
25 | def getRoots(a: Double, b: Double, c: Double): EitherT[Future, String, (Double, Double)] = {
26 | val D = discriminant(a, b, c)
27 | for {
28 | d <- EitherT(deepSqrt(D).map(_.left.map(_ => negativeDiscriminantError(D))))
29 | lc <- EitherT(Monad[Future].unit(validateLeadingCoefficient(a)))
30 | } yield rootHelper(lc, b, d)
31 | }
32 |
33 | def addSquareRoots(x1: Double, x2: Double): EitherT[Future, String, Double] =
34 | Monad[EitherTMonad].map2(
35 | EitherT(deepSqrt(x1)).mapEither(_.left.map(_ => negativeRootError(x1))),
36 | EitherT(deepSqrt(x2)).mapEither(_.left.map(_ => negativeRootError(x2)))
37 | )(_ + _)
38 |
39 | def getSumOfSquareRoots(a: Double, b: Double, c: Double): EitherT[Future, String, Double] =
40 | for {
41 | roots <- getRoots(a, b, c)
42 | sum <- addSquareRoots(roots._1, roots._2)
43 | } yield sum
44 |
45 | def printSumOfSquareRoots(a: Double, b: Double, c: Double): Unit =
46 | getSumOfSquareRoots(a, b, c).run.onComplete {
47 | case Success(Right(sum)) => println(s"Result: $sum")
48 | case Success(Left(error)) => println(error)
49 | case Failure(exception) => println(exception.getMessage)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/src/main/scala/example/FutureEitherPractice.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import example.MonadInstances._
4 |
5 | import scala.concurrent.ExecutionContext.Implicits.global
6 | import scala.concurrent.Future
7 | import scala.util.{Failure, Success}
8 |
9 | trait FutureEitherPractice {
10 | type EitherTMonad[+A] = EitherT[Future, String, A]
11 |
12 | def sqrt(x: Double): Either[String, Double] =
13 | if (x >= 0) Right(librarySqrt(x)) else Left("Square root of negative number")
14 |
15 | def deepSqrt(x: Double): Future[Either[String, Double]] = Future {
16 | val time = 2000L
17 | Thread.sleep(time)
18 | println("Thinking...")
19 | sqrt(x)
20 | }
21 |
22 | def validateLeadingCoefficient(lc: Double): Either[String, Double] =
23 | if (lc != 0) Right(lc) else Left("Leading coefficient equals zero")
24 |
25 | def getRoots(a: Double, b: Double, c: Double): EitherT[Future, String, (Double, Double)]
26 |
27 | def addSquareRoots(x1: Double, x2: Double): EitherT[Future, String, Double]
28 |
29 | def getSumOfSquareRoots(a: Double, b: Double, c: Double): EitherT[Future, String, Double]
30 |
31 | def printSumOfSquareRoots(a: Double, b: Double, c: Double): Unit =
32 | getSumOfSquareRoots(a, b, c).run.onComplete {
33 | case Success(Right(sum)) => println(s"Result: $sum")
34 | case Success(Left(error)) => println(error)
35 | case Failure(exception) => println(exception.getMessage)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/src/main/scala/example/FutureOptionExample.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import example.MonadInstances._
4 |
5 | import scala.concurrent.ExecutionContext.Implicits.global
6 | import scala.concurrent.Future
7 | import scala.util.{Failure, Success}
8 |
9 | trait FutureOptionExample {
10 | type OptionTMonad[+A] = OptionT[Future, A]
11 |
12 | def sqrt(x: Double): Option[Double] = if (x >= 0) Some(librarySqrt(x)) else None
13 |
14 | def deepSqrt(x: Double): Future[Option[Double]] = Future {
15 | val time = 2000L
16 | Thread.sleep(time)
17 | println("Thinking...")
18 | sqrt(x)
19 | }
20 |
21 | def validateLeadingCoefficient(lc: Double): Option[Double] =
22 | if (lc != 0) Some(lc) else None
23 |
24 | def getRoots(a: Double, b: Double, c: Double): OptionT[Future, (Double, Double)] = {
25 | val D = discriminant(a, b, c)
26 | for {
27 | d <- OptionT(deepSqrt(D))
28 | lc <- OptionT(Monad[Future].unit(validateLeadingCoefficient(a)))
29 | } yield rootHelper(lc, b, d)
30 | }
31 |
32 | def addSquareRoots(x1: Double, x2: Double): OptionT[Future, Double] =
33 | Monad[OptionTMonad].map2(OptionT(deepSqrt(x1)), OptionT(deepSqrt(x2)))(_ + _)
34 |
35 | def getSumOfSquareRoots(a: Double, b: Double, c: Double): OptionT[Future, Double] =
36 | for {
37 | roots <- getRoots(a, b, c)
38 | sum <- addSquareRoots(roots._1, roots._2)
39 | } yield sum
40 |
41 | def printSumOfSquareRoots(a: Double, b: Double, c: Double): Unit =
42 | getSumOfSquareRoots(a, b, c).run.onComplete {
43 | case Success(Some(sum)) => println(s"Result: $sum")
44 | case Success(None) => println("No solution")
45 | case Failure(e) => println(s"Something happened: ${e.getMessage}")
46 | }
47 | }
48 |
49 |
50 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/src/main/scala/example/FutureOptionPractice.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import scala.concurrent.ExecutionContext.Implicits.global
4 | import scala.concurrent.Future
5 | import scala.util.{Failure, Success}
6 |
7 | import MonadInstances._
8 |
9 | trait FutureOptionPractice {
10 | type OptionTMonad[+A] = OptionT[Future, A]
11 |
12 | def sqrt(x: Double): Option[Double] = if (x >= 0) Some(librarySqrt(x)) else None
13 |
14 | def deepSqrt(x: Double): Future[Option[Double]] = Future {
15 | val time = 2000L
16 | Thread.sleep(time)
17 | println("Thinking...")
18 | sqrt(x)
19 | }
20 |
21 | def validateLeadingCoefficient(lc: Double): Option[Double] =
22 | if (lc != 0) Some(lc) else None
23 |
24 | def getRoots(a: Double, b: Double, c: Double): OptionT[Future, (Double, Double)]
25 |
26 | def addSquareRoots(x1: Double, x2: Double): OptionT[Future, Double]
27 |
28 | def getSumOfSquareRoots(a: Double, b: Double, c: Double): OptionT[Future, Double]
29 |
30 | def printSumOfSquares(a: Double, b: Double, c: Double): Unit =
31 | getSumOfSquareRoots(a, b, c).run.onComplete {
32 | case Success(Some(sum)) => println(s"Result: $sum")
33 | case Success(None) => println("No solution")
34 | case Failure(e) => println(s"Something happened: ${e.getMessage}")
35 | }
36 | }
37 |
38 |
39 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/src/main/scala/example/Monad.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import scala.concurrent.{ExecutionContext, Future}
4 | import scala.language.{higherKinds, implicitConversions, reflectiveCalls}
5 |
6 | trait Monad[F[+ _]] {
7 | def unit[A](a: => A): F[A]
8 |
9 | def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
10 |
11 | def map[A, B](fa: F[A])(f: A => B): F[B] = flatMap(fa)(a => unit(f(a)))
12 |
13 | def join[A](ffa: F[F[A]]): F[A] = flatMap(ffa)(identity)
14 |
15 | def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] =
16 | flatMap(fa)(a => map(fb)(b => f(a, b)))
17 | }
18 |
19 | object Monad {
20 | def apply[F[+ _]](implicit monadInstance: Monad[F]): Monad[F] = monadInstance
21 | }
22 |
23 | object MonadInstances {
24 |
25 | implicit class MonadOps[F[+ _] : Monad, A](fa: F[A]) {
26 | def flatMap[B](f: A => F[B]): F[B] = Monad[F].flatMap(fa)(f)
27 |
28 | def map[B](f: A => B): F[B] = Monad[F].map(fa)(f)
29 |
30 | def join[B](implicit ev: F[A] =:= F[F[B]]): F[B] = Monad[F].join[B](fa)
31 |
32 | def map2[B, C](fb: F[B])(f: (A, B) => C): F[C] = Monad[F].map2(fa, fb)(f)
33 | }
34 |
35 | implicit val optionInstance: Monad[Option] = new Monad[Option] {
36 | override def unit[A](a: => A): Option[A] = Some(a)
37 |
38 | override def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] =
39 | fa.flatMap(f)
40 | }
41 |
42 | implicit val listInstance: Monad[List] = new Monad[List] {
43 | override def unit[A](a: => A): List[A] = List(a)
44 |
45 | override def flatMap[A, B](fa: List[A])(f: A => List[B]): List[B] =
46 | fa.flatMap(f)
47 | }
48 |
49 | implicit def eitherInstance[S]: Monad[({type T[+A] = Either[S, A]})#T] =
50 | new Monad[({type T[+A] = Either[S, A]})#T] {
51 | override def unit[A](a: => A): Either[S, A] = Right(a)
52 |
53 | override def flatMap[A, B](fa: Either[S, A])(f: A => Either[S, B]): Either[S, B] =
54 | fa.flatMap(f)
55 | }
56 |
57 | implicit def futureInstance(implicit ex: ExecutionContext): Monad[Future] =
58 | new Monad[Future] {
59 | override def unit[A](a: => A): Future[A] = Future.successful(a)
60 |
61 | override def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] =
62 | fa.flatMap(f)
63 | }
64 |
65 | implicit def optionTransformerInstance[F[+ _] : Monad]: Monad[({type T[+A] = OptionT[F, A]})#T] =
66 | new Monad[({type T[+A] = OptionT[F, A]})#T] {
67 | override def unit[A](a: => A): OptionT[F, A] = OptionT(Monad[F].unit(Some(a)))
68 |
69 | override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] =
70 | OptionT(fa.run.map(_.map(f)))
71 |
72 | override def join[A](ffa: OptionT[F, OptionT[F, A]]): OptionT[F, A] =
73 | OptionT(ffa.run.map(_.map(_.run).traverseM(identity)).flatMap(identity))
74 |
75 | override def flatMap[A, B](fa: OptionT[F, A])(f: A => OptionT[F, B]): OptionT[F, B] =
76 | join(map(fa)(f))
77 | }
78 |
79 | implicit def eitherTransformerInstance[F[+ _] : Monad, S]: Monad[({type T[+A] = EitherT[F, S, A]})#T] =
80 | new Monad[({type T[+A] = EitherT[F, S, A]})#T] {
81 | override def unit[A](a: => A): EitherT[F, S, A] = EitherT(Monad[F].unit(Right(a)))
82 |
83 | override def map[A, B](fa: EitherT[F, S, A])(f: A => B): EitherT[F, S, B] =
84 | EitherT(fa.run.map(_.map(f)))
85 |
86 | override def join[A](ffa: EitherT[F, S, EitherT[F, S, A]]): EitherT[F, S, A] =
87 | EitherT(ffa.run.map(_.map(_.run).traverseM(identity)).flatMap(identity))
88 |
89 | override def flatMap[A, B](fa: EitherT[F, S, A])(f: A => EitherT[F, S, B]): EitherT[F, S, B] =
90 | join(map(fa)(f))
91 | }
92 |
93 | implicit class ListSequence[F[+ _] : Monad, A](list: List[F[A]]) {
94 | def sequence: F[List[A]] =
95 | list.foldRight[F[List[A]]](Monad[F].unit(Nil))((next, acc) => Monad[F].map2(next, acc)(_ :: _))
96 | }
97 |
98 | implicit class OptionSequence[F[+ _] : Monad, A](option: Option[F[A]]) {
99 | def sequence: F[Option[A]] = option match {
100 | case Some(fa) => fa.map(Some(_))
101 | case None => Monad[F].unit(None)
102 | }
103 | }
104 |
105 | implicit class EitherSequence[F[+ _] : Monad, S, A](either: Either[S, F[A]]) {
106 | def sequence: F[Either[S, A]] = either match {
107 | case Right(fa) => fa.map(Right(_))
108 | case Left(s) => Monad[F].unit(Left(s))
109 | }
110 | }
111 |
112 | implicit class ListTraverse[A](list: List[A]) {
113 | def traverse[F[+ _] : Monad, B](f: A => F[B]): F[List[B]] = list.map(f).sequence
114 | }
115 |
116 | implicit class OptionTraverse[A](op: Option[A]) {
117 | def traverse[F[+ _] : Monad, B](f: A => F[B]): F[Option[B]] = op.map(f).sequence
118 | }
119 |
120 | implicit class OptionTraverseM[A](op: Option[A]) {
121 | def traverseM[F[+ _] : Monad, B](f: A => F[Option[B]]): F[Option[B]] = op.traverse(f).map(_.flatten)
122 | }
123 |
124 | implicit class EitherTraverse[S, A](either: Either[S, A]) {
125 | def traverse[F[+ _] : Monad, B](f: A => F[B]): F[Either[S, B]] = either.map(f).sequence
126 | }
127 |
128 | implicit class EitherTraverseM[S, A](either: Either[S, A]) {
129 | def traverseM[F[+ _] : Monad, B](f: A => F[Either[S, B]]): F[Either[S, B]] =
130 | either.traverse(f).map(_.flatMap(identity))
131 | }
132 |
133 | }
134 |
135 | class OptionT[F[+ _] : Monad, +A](val run: F[Option[A]]) {
136 | def mapOption[B](f: Option[A] => Option[B]): OptionT[F, B] =
137 | OptionT(Monad[F].map(run)(f))
138 | }
139 |
140 | object OptionT {
141 |
142 | import MonadInstances._
143 |
144 | def apply[F[+ _] : Monad, A](run: F[Option[A]]): OptionT[F, A] = new OptionT(run)
145 |
146 | implicit class OptionTOps[F[+ _] : Monad, +A](optionT: OptionT[F, A]) {
147 | type T[+X] = OptionT[F, X]
148 |
149 | def map[B](f: A => B): T[B] = Monad[T].map(optionT)(f)
150 |
151 | def flatMap[B](f: A => T[B]): T[B] = Monad[T].flatMap(optionT)(f)
152 |
153 | def map2[B, C](fb: T[B])(f: (A, B) => C): T[C] = Monad[T].map2(optionT, fb)(f)
154 | }
155 |
156 | }
157 |
158 | class EitherT[F[+ _] : Monad, S, +A](val run: F[Either[S, A]]) {
159 | def mapEither[B](f: Either[S, A] => Either[S, B]): EitherT[F, S, B] =
160 | EitherT(Monad[F].map(run)(f))
161 | }
162 |
163 | object EitherT {
164 |
165 | import MonadInstances._
166 |
167 | def apply[F[+ _] : Monad, S, A](run: F[Either[S, A]]): EitherT[F, S, A] = new EitherT(run)
168 |
169 | implicit class EitherTOps[F[+ _] : Monad, S, +A](eitherT: EitherT[F, S, A]) {
170 | type T[+X] = EitherT[F, S, X]
171 |
172 | def map[B](f: A => B): T[B] = Monad[T].map(eitherT)(f)
173 |
174 | def flatMap[B](f: A => T[B]): T[B] = Monad[T].flatMap(eitherT)(f)
175 |
176 | def map2[B, C](fb: T[B])(f: (A, B) => C): T[C] = Monad[T].map2(eitherT, fb)(f)
177 | }
178 |
179 | }
180 |
181 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/src/main/scala/example/OptionExample.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import example.MonadInstances._
4 |
5 | trait OptionExample {
6 |
7 | def sqrt(x: Double): Option[Double] =
8 | if (x >= 0) Some(librarySqrt(x)) else None
9 |
10 | def validateLeadingCoefficient(lc: Double): Option[Double] =
11 | if (lc != 0) Some(lc) else None
12 |
13 | def getRoots(a: Double, b: Double, c: Double): Option[(Double, Double)] = {
14 | val D = discriminant(a, b, c)
15 | for {
16 | d: Double <- sqrt(D)
17 | lc: Double <- validateLeadingCoefficient(a)
18 | } yield rootHelper(lc, b, d)
19 | }
20 |
21 | def addSquareRoots(x1: Double, x2: Double): Option[Double] =
22 | Monad[Option].map2(sqrt(x1), sqrt(x2))(_ + _)
23 |
24 | def getSumOfSquares(a: Double, b: Double, c: Double): Option[Double] =
25 | for {
26 | roots: (Double, Double) <- getRoots(a, b, c)
27 | sum: Double <- addSquareRoots(roots._1, roots._2)
28 | } yield sum
29 |
30 | def printSumOfSquareRoots(a: Double, b: Double, c: Double): Unit =
31 | getSumOfSquares(a, b, c) match {
32 | case Some(sum) => println(s"Result: $sum")
33 | case None => println("No solution")
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/src/main/scala/example/OptionPractice.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import MonadInstances._
4 |
5 | trait OptionPractice {
6 |
7 | def sqrt(x: Double): Option[Double]
8 |
9 | def validateLeadingCoefficient(lc: Double): Option[Double]
10 |
11 | def getRoots(a: Double, b: Double, c: Double): Option[(Double, Double)]
12 |
13 | def addSquareRoots(x1: Double, x2: Double): Option[Double]
14 |
15 | def getSumOfSquares(a: Double, b: Double, c: Double): Option[Double]
16 |
17 | def printSumOfSquareRoots(a: Double, b: Double, c: Double): Unit =
18 | getSumOfSquares(a, b, c) match {
19 | case Some(sum) => println(s"Result: $sum")
20 | case None => println("No solution")
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/monad-examples/src/main/scala/example/package.scala:
--------------------------------------------------------------------------------
1 | import java.util.concurrent.TimeUnit
2 |
3 | import scala.concurrent.duration.FiniteDuration
4 | import scala.concurrent.{Await, ExecutionContext, Future}
5 |
6 | package object example {
7 | val leadingCoefficientError = "Error: leading coefficient equals zero"
8 |
9 | def squareRootOfNegativeNumberError(x: Double) = s"Error: square root of negative number $x"
10 |
11 | def negativeDiscriminantError(D: Double) = s"Error: negative discriminant $D"
12 |
13 | def negativeRootError(root: Double) = s"Error: negative root $root"
14 |
15 | def discriminant(a: Double, b: Double, c: Double): Double = b * b - 4 * a * c
16 |
17 | def librarySqrt(x: Double): Double = math.pow(x, 0.5)
18 |
19 | def rootHelper(a: Double, b: Double, d: Double): (Double, Double) = {
20 | val x1 = (-b + d) / (2 * a)
21 | val x2 = (-b - d) / (2 * a)
22 | (x1, x2)
23 | }
24 |
25 | implicit class FutureOps[A](future: Future[A]) {
26 | def evaluate: A = Await.result(future, FiniteDuration(5, TimeUnit.SECONDS))
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/lectures/examples/12-monads-and-functors/state-monad.md:
--------------------------------------------------------------------------------
1 | По време на лекциите не остана време да поговорим за State монадата, затова ще я опишем тук. Целия пример може да видите в кода от лекциите в [`RNG.scala`](lecture-code/src/main/scala/effects/state/RNG.scala).
2 |
3 | Като пример ще разгледаме генератор на псевдослучайни числа, като типично нещо, обикновено имплементирано чрез mutable state. Стандартният начин на използване е:
4 |
5 | ```scala
6 | val random = new Random
7 |
8 | val a = random.nextInt
9 | val b = random.nextInt
10 | val c = random.nextBoolean
11 | ```
12 |
13 | Като тук всяко извикване на `nextInt` и `nextBoolean` променя състоянието на `random`, с което се губи и референтната прозрачност.
14 |
15 | Типично функционално решение на този проблем би изглеждало така:
16 |
17 | ```scala
18 | val rng1 = RNG(initialSeed)
19 |
20 | val (rng2, a) = rng1.nextInt
21 | val (rng3, b) = rng2.nextInt
22 | val (_, c) = rng3.nextBoolean
23 | ```
24 |
25 | Тоест `nextInt` и `nextBoolean` връщат не само следващата случайна стойност, но и следващото състояние на генератора на случайни числа. За разлика от първия пример, те по никакъв начин не променят инстанцията, сочена от `rng1`. Така получаваме решение без mutability.
26 |
27 | `RNG.nextInt` е със сигнатура `def nextInt: (RNG, Int)` ([код](lecture-code/src/main/scala/effects/state/RNG.scala), имплементацията е от червената книга за Scala)
28 |
29 | Проблемът на решението е, че имаме досадно влачене на състояние, което е податливо на грешки (трябва да внимаваме да реферираме правилната версия на генератора).
30 |
31 | Тази ситуация бихме имали при всеки възможен вид състояние, та да пробваме да го изнесем в ефект. Функциолно генерирането на стойност и ново състояние от текущо състояние изразяваме чрез функция `S => (S, A)`, където `S` е типа за състояние, а `A` на генерираната стойност. Нека да сложим тази функция в тип, който ще направим ефектен:
32 |
33 | ```scala
34 | case class State[S, A](run: S => (S, A))
35 | ```
36 |
37 | Да имплементираме монадна инстанция за този тип, която да ни позволи да композираме генерирането на стойности, всяка от съответното ѝ състояние. За примера горе с генератора на случайни числа това значи всяко последвашо случайно число да бъде генерирано от правилното последващо състояние на генератора. Инстанцията ще бъде следната:
38 |
39 | ```scala
40 | object State {
41 | implicit def stateMonad[S] = new Monad[State[S, _]] {
42 | def flatMap[A, B](fa: State[S, A])(f: A => State[S, B]): State[S, B] = State { s1 =>
43 | val (s2, a) = fa.run(s1)
44 | f(a).run(s2)
45 | }
46 |
47 | def unit[A](a: => A): State[S, A] = State(s => (s, a))
48 | }
49 | }
50 | ```
51 |
52 | Разглеждаме `State` като ефект по втория си параметър, а за всеки тип `S` той ще има различна монадна инстанция. Както говорихме на лекциите, синтаксисът `State[S, _]` ще работи едва от версия 3 на Scala, за текущата версия се налага да използва по-сложен синтаксис с ламбда за типове, който може да [видите тук](lecture-code/src/main/scala/effects/state/State.scala).
53 |
54 | Забележете, че кодът на `flatMap` много прилича на следните два реда, които бяха по-горе (върнатият резултат също е двойка):
55 |
56 | ```scala
57 | val (rng2, a) = rng1.nextInt
58 | val (rng3, b) = rng2.nextInt
59 | ```
60 |
61 | Имплементацията на `unit` винаги генерира `a`, независимо от това какво е състоянието `s`, а самото `s` остава непроменено. Тоест това е `State` инстанция, която винаги генерира константа.
62 |
63 | Да се върнем към примера със случайните числа и да използваме `State` за него. Можем да създадем `State`, генериращ случаен `Int`, по следния начин:
64 |
65 | ```scala
66 | val nextInt: State[RNG, Int] = State((rng: RNG) => rng.nextInt)
67 | ```
68 |
69 | Той приема текущо състояние на генератора `RNG` и връща двойка от новото състояние на генератора и генерираното число.
70 |
71 | По лесен начин можем да трансфомираме този `State` обект към нов, който да генерира `Boolean` стойности:
72 |
73 | ```scala
74 | val nextBoolean: State[RNG, Boolean] = nextInt.map(_ >= 0)
75 | ```
76 |
77 | Тук използваме операцията `map`, която получаваме благодарение на това, че предоставихме монадна инстанция на `State`. `map` получава генерираното случайно число и го преобразува в `true` или `false` в зависимост от това дали е неотрицателно или не. Забележете, че `map` не се интересува от състоянието на генератора, то се обновява автоматично чрез монадата (`flatMap` се грижи за това вътрешно).
78 |
79 | Сега вече можем да направим следното:
80 |
81 | ```scala
82 | val randomTuple = for {
83 | a <- nextInt
84 | b <- nextInt
85 | c <- nextBoolean
86 | } yield (a, b, a + b, c)
87 | ```
88 |
89 | Тук отново имаме последователни извиквания на `nextInt` (два пъти) и `nextBoolean`, но за разлика от първоначалния чисто функционален код, вече не се налага да предаваме състоянието на генератора ръчно – монадната инстанция на `State` прави това автоматично за нас.
90 |
91 | При горния код все още не се изпълнява нищо. Може да стартираме изчислението по следния начин:
92 |
93 | ```scala
94 | val intialRng = RNG(System.currentTimeMillis) // use current time as random seed
95 | val (_, result) = randomTuple.run(intialRng)
96 | ```
97 |
98 | Така `result` съдържа генерираната случайна четворка.
99 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/build.sbt:
--------------------------------------------------------------------------------
1 | name := "modularity"
2 | version := "0.1"
3 |
4 | libraryDependencies ++= Seq(
5 | "com.typesafe" % "config" % "1.3.4",
6 | "org.scalatest" %% "scalatest" % "3.0.5" % "test"
7 | )
8 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.6
2 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/application.conf:
--------------------------------------------------------------------------------
1 | application.d-version = 1
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/Application.scala:
--------------------------------------------------------------------------------
1 | package modularity
2 |
3 | import com.typesafe.config.ConfigFactory
4 | import modularity.a.{A3, AModule}
5 | import modularity.b.BModule
6 | import modularity.c.CModule
7 | import modularity.d.DModule
8 |
9 |
10 | object Application extends AModule
11 | with BModule
12 | with CModule
13 | with DModule {
14 |
15 | // Loading TypeSafe config from src/main/resources/application.conf
16 | // More info: https://github.com/lightbend/config
17 | lazy val config = ConfigFactory.load()
18 |
19 | def main(args: Array[String]): Unit = {
20 | println(c2.doSomething)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/a/A1.scala:
--------------------------------------------------------------------------------
1 | package modularity.a
2 |
3 | class A1
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/a/A2.scala:
--------------------------------------------------------------------------------
1 | package modularity.a
2 |
3 | class A2
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/a/A3.scala:
--------------------------------------------------------------------------------
1 | package modularity.a
2 |
3 | class A3(a1: A1, a2: A2) {
4 | println(a1, a2)
5 | }
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/a/AModule.scala:
--------------------------------------------------------------------------------
1 | package modularity.a
2 |
3 | trait AModule {
4 | lazy val a3 = new A3(a1, a2)
5 | lazy val a1 = new A1
6 | lazy val a2 = new A2
7 | }
8 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/b/B1.scala:
--------------------------------------------------------------------------------
1 | package modularity.b
2 |
3 | class B1
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/b/B2.scala:
--------------------------------------------------------------------------------
1 | package modularity.b
2 |
3 | import modularity.a.A1
4 |
5 | class B2(b1: B1, a1: A1)
6 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/b/BModule.scala:
--------------------------------------------------------------------------------
1 | package modularity.b
2 |
3 | import modularity.a.{A1, AModule}
4 |
5 | trait BModule {
6 | def a1: A1
7 |
8 | lazy val b1 = new B1
9 | lazy val b2 = new B2(b1, a1)
10 | }
11 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/c/C1.scala:
--------------------------------------------------------------------------------
1 | package modularity.c
2 |
3 | class C1
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/c/C2.scala:
--------------------------------------------------------------------------------
1 | package modularity.c
2 |
3 | import modularity.a.A3
4 | import modularity.b.B2
5 |
6 | class C2(a3: A3, b2: B2, c1: C1) {
7 | def doSomething = println("Hello form c2")
8 | }
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/c/CModule.scala:
--------------------------------------------------------------------------------
1 | package modularity.c
2 |
3 | import modularity.a.A3
4 | import modularity.b.B2
5 |
6 | trait CModule {
7 | def a3: A3
8 | def b2: B2
9 |
10 | lazy val c1 = new C1
11 | lazy val c2 = new C2(a3, b2, c1)
12 | }
13 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/d/D.scala:
--------------------------------------------------------------------------------
1 | package modularity.d
2 |
3 | trait D
4 | class D1 extends D
5 | class D2 extends D
6 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/modularity/src/main/scala/modularity/d/DModule.scala:
--------------------------------------------------------------------------------
1 | package modularity.d
2 |
3 | import com.typesafe.config.Config
4 | import modularity.Application.config
5 | import modularity.{D1, D2}
6 |
7 | trait DModule {
8 | def config: Config
9 |
10 | // Runtime decision based on config:
11 | lazy val d = {
12 | val dVersion = Option(config.getInt("application.d-version")).getOrElse(2)
13 | if (dVersion == 1) new D1 else new D2
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/ShoppingApp.scala:
--------------------------------------------------------------------------------
1 | import authentication.AuthenticatedAction
2 | import controllers.{ApplicationController, InventoryController, ShoppingController, UserController}
3 | import inventory.{InventoryModule, ProductSku}
4 | import play.api.ApplicationLoader.Context
5 | import play.api.mvc.BodyParsers
6 | import play.api.routing.Router
7 | import play.api.routing.sird._
8 | import play.api.{Application, ApplicationLoader, BuiltInComponentsFromContext}
9 | import play.filters.HttpFiltersComponents
10 | import shopping.ShoppingModule
11 | import user.UsersModule
12 |
13 | class ShoppingAppLoader extends ApplicationLoader {
14 | def load(context: Context): Application = new ShoppingApp(context).application
15 | }
16 |
17 | class ShoppingApp(context: Context) extends BuiltInComponentsFromContext(context)
18 | with HttpFiltersComponents
19 | with UsersModule
20 | with InventoryModule
21 | with ShoppingModule {
22 |
23 | lazy val defaultBodyParsers = new BodyParsers.Default(playBodyParsers)
24 |
25 | lazy val authenticationAction = new AuthenticatedAction(registeredUsersRepository, defaultBodyParsers)
26 |
27 | lazy val applicationController = new ApplicationController(controllerComponents)
28 | lazy val userController = new UserController(controllerComponents, registeredUsersRepository, authenticationAction)
29 | lazy val inventoryController = new InventoryController(inventoryManager, controllerComponents)
30 | lazy val shoppingController = new ShoppingController(shop, controllerComponents, authenticationAction)
31 |
32 | lazy val router: Router = Router.from {
33 | case GET(p"/") => applicationController.index
34 |
35 | case GET(p"/current-user") => userController.user
36 | case POST(p"/login") => userController.login
37 | case POST(p"/logout") => userController.logout
38 | case POST(p"/users") => userController.register
39 |
40 | case GET(p"/products") => inventoryController.inventory
41 | case GET(p"/products/$sku") => inventoryController.product(ProductSku(sku))
42 |
43 | case GET(p"/current-user/cart") => shoppingController.cart
44 | case POST(p"/current-user/cart") => shoppingController.addToCart
45 | case POST(p"/current-user/cart/order") => shoppingController.placeOrder
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/authentication/AuthenticatedAction.scala:
--------------------------------------------------------------------------------
1 | package authentication
2 |
3 | import play.api.mvc._
4 | import user.RegisteredUsersRepository
5 |
6 | import scala.concurrent.{ExecutionContext, Future}
7 |
8 | class AuthenticatedAction(registeredUsersRepository: RegisteredUsersRepository,
9 | val parser: BodyParsers.Default)
10 | (implicit val executionContext: ExecutionContext)
11 | extends ActionBuilder[AuthenticatedRequest, AnyContent] with ActionRefiner[Request, AuthenticatedRequest] {
12 |
13 | protected def refine[A](request: Request[A]): Future[Either[Result, AuthenticatedRequest[A]]] = {
14 | request.session.get("user") match {
15 | case Some(email) =>
16 | registeredUsersRepository.retrieveUser(email).map {
17 | case Some(user) => Right(new AuthenticatedRequest[A](user, request))
18 | case None => Left(Results.Unauthorized)
19 | }
20 | case None =>
21 | Future.successful(Left(Results.Unauthorized))
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/authentication/AuthenticatedRequest.scala:
--------------------------------------------------------------------------------
1 | package authentication
2 |
3 | import play.api.mvc.{Request, WrappedRequest}
4 | import user.User
5 |
6 | class AuthenticatedRequest[A](val user: User, request: Request[A]) extends WrappedRequest[A](request)
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/controllers/ApplicationController.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import play.api.mvc.{AbstractController, ControllerComponents}
4 |
5 | class ApplicationController(cc: ControllerComponents) extends AbstractController(cc) {
6 | def index = Action {
7 | Ok("Hello World")
8 | }
9 | }
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/controllers/InventoryController.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import inventory.{InventoryManager, ProductSku}
4 | import play.api.libs.json.Json
5 | import play.api.mvc.{AbstractController, ControllerComponents}
6 |
7 | import scala.concurrent.ExecutionContext
8 |
9 | class InventoryController(inventoryManager: InventoryManager,
10 | cc: ControllerComponents)
11 | (implicit ex: ExecutionContext) extends AbstractController(cc) {
12 | def inventory = Action.async {
13 | inventoryManager.getInventory.map(inventory => Ok(Json.toJson(inventory)))
14 | }
15 |
16 | def product(sku: ProductSku) = Action.async {
17 | inventoryManager.productStock(sku).map {
18 | case Some(productStock) => Ok(Json.toJson(productStock))
19 | case _ => NotFound
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/controllers/ShoppingController.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import authentication.AuthenticatedAction
4 | import controllers.ShoppingController.ShoppingCartSessionKey
5 | import play.api.libs.json.Json
6 | import play.api.mvc.{AbstractController, ControllerComponents, Session}
7 | import shopping.{NotEnoughQuantityForOrder, OrderLine, Shop, ShoppingCart}
8 |
9 | import scala.concurrent.ExecutionContext
10 |
11 | class ShoppingController(shop: Shop,
12 | cc: ControllerComponents,
13 | authenticatedAction: AuthenticatedAction)
14 | (implicit ex: ExecutionContext) extends AbstractController(cc) {
15 | def addToCart = authenticatedAction(parse.json[OrderLine]) { request =>
16 | val cart = getShoppingCart(request.session)
17 | val orderLine = request.body
18 |
19 | val newCart = cart.add(orderLine)
20 | val newCartJson = Json.toJson(newCart)
21 |
22 | Ok(newCartJson).withSession(ShoppingCartSessionKey -> newCartJson.toString)
23 | }
24 |
25 | def cart = authenticatedAction { request =>
26 | Ok(Json.toJson(getShoppingCart(request.session)))
27 | }
28 |
29 | def placeOrder = authenticatedAction.async { request =>
30 | val cart = getShoppingCart(request.session)
31 |
32 | shop.placeOrder(???, cart)
33 | .map(order => Ok(Json.toJson(order)))
34 | .recover {
35 | case NotEnoughQuantityForOrder => Conflict
36 | }
37 | }
38 |
39 | def getShoppingCart(session: Session): ShoppingCart = {
40 | session.get(ShoppingCartSessionKey)
41 | .map(Json.parse(_).validate[ShoppingCart])
42 | .flatMap(_.asOpt)
43 | .getOrElse(ShoppingCart())
44 | }
45 | }
46 |
47 | object ShoppingController {
48 | val ShoppingCartSessionKey = "shopping-cart"
49 | }
50 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/controllers/UserController.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import authentication.AuthenticatedAction
4 | import play.api.libs.json.Json
5 | import play.api.mvc.{AbstractController, ControllerComponents}
6 | import user.{RegisteredUsersRepository, User, UserAlreadyExists, UsersRegistry}
7 |
8 | import scala.concurrent.ExecutionContext
9 |
10 | case class UserCredentials(email: String, password: String) {
11 | }
12 |
13 | object UserCredentials {
14 | implicit val userCredentialsRead = Json.reads[UserCredentials]
15 | }
16 |
17 | class UserController(cc: ControllerComponents,
18 | registeredUsersRepository: RegisteredUsersRepository,
19 | authenticatedAction: AuthenticatedAction)
20 | (implicit ex: ExecutionContext) extends AbstractController(cc) {
21 | def register = Action.async(parse.json[User]) { request =>
22 | registeredUsersRepository.registerUser(request.body)
23 | .map { _ => Ok }
24 | .recover {
25 | case UserAlreadyExists(_) => Conflict
26 | }
27 | }
28 |
29 | def login = Action.async(parse.json[UserCredentials]) { request =>
30 | val credentials = request.body
31 | registeredUsersRepository.retrieveUser(credentials.email).map {
32 | case Some(user) if user.password == credentials.password =>
33 | Ok.withSession(
34 | "user" -> user.email
35 | )
36 | case _ => Unauthorized
37 | }
38 | }
39 |
40 | def user = authenticatedAction { request =>
41 | Ok(Json.toJson(request.user))
42 | }
43 |
44 | def logout = Action {
45 | Ok.withNewSession
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/inventory/Inventory.scala:
--------------------------------------------------------------------------------
1 | package inventory
2 |
3 | import play.api.libs.json.{JsValue, Json, Writes}
4 |
5 | case class ProductStock(product: Product, quantity: Int) {
6 | def adjustWith(n: Int) = ProductStock(product, quantity + n)
7 | }
8 |
9 | case class Inventory(productsStock: Map[ProductSku, ProductStock]) {
10 | def isAdjustmentApplicable(inventoryAdjustment: InventoryAdjustment): Boolean = {
11 | inventoryAdjustment.adjustments.forall {
12 | case (product, quantityAdjustment) =>
13 | val availableQuantity = productsStock.get(product).map(_.quantity).getOrElse(0)
14 |
15 | availableQuantity + quantityAdjustment >= 0
16 | }
17 | }
18 |
19 | def adjust(inventoryAdjustment: InventoryAdjustment): Option[Inventory] = {
20 | if (isAdjustmentApplicable(inventoryAdjustment)) Some {
21 | Inventory {
22 | inventoryAdjustment.adjustments.foldLeft(productsStock) { (inventory, adjustment) =>
23 | val (product, quantity) = adjustment
24 |
25 | inventory + (product -> inventory(product).adjustWith(quantity))
26 | }
27 | }
28 | }
29 | else None
30 | }
31 | }
32 |
33 | object ProductStock {
34 | implicit val productStockFormat = Json.format[ProductStock]
35 | }
36 |
37 | object Inventory {
38 | implicit val inventoryWrites = new Writes[Inventory] {
39 | def writes(inventory: Inventory): JsValue = Json.toJson(inventory.productsStock.values)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/inventory/InventoryAdjustment.scala:
--------------------------------------------------------------------------------
1 | package inventory
2 |
3 | case class InventoryAdjustment(adjustments: Map[ProductSku, Int])
4 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/inventory/InventoryManager.scala:
--------------------------------------------------------------------------------
1 | package inventory
2 |
3 | import scala.concurrent.{ExecutionContext, Future}
4 |
5 | class InventoryManager(startingInventory: Inventory)
6 | (implicit ex: ExecutionContext) {
7 | @volatile private var inventory = startingInventory
8 |
9 | def getInventory: Future[Inventory] = Future {
10 | inventory
11 | }
12 |
13 | def productStock(sku: ProductSku): Future[Option[ProductStock]] = Future {
14 | inventory.productsStock.get(sku)
15 | }
16 |
17 | def applyAdjustment(inventoryAdjustment: InventoryAdjustment): Future[AdjustmentResult] = Future {
18 | this.synchronized {
19 | inventory.adjust(inventoryAdjustment) match {
20 | case Some(newInventory) =>
21 | inventory = newInventory
22 | SuccessfulAdjustment
23 | case None => NotEnoughQuantity
24 | }
25 | }
26 | }
27 | }
28 |
29 | sealed trait AdjustmentResult
30 | case object SuccessfulAdjustment extends AdjustmentResult
31 | case object NotEnoughQuantity extends AdjustmentResult
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/inventory/InventoryModule.scala:
--------------------------------------------------------------------------------
1 | package inventory
2 |
3 | import scala.concurrent.ExecutionContext
4 |
5 | trait InventoryModule {
6 | def executionContext: ExecutionContext
7 |
8 | private val initialInventory = Inventory(Map(
9 | ProductSku("123") -> ProductStock(Product(ProductSku("123"), "Scala bar", "Tasty scala bar", 40), 30),
10 | ProductSku("200") -> ProductStock(Product(ProductSku("200"), "Laptop", "Some laptop", 1000), 22),
11 | ProductSku("300") -> ProductStock(Product(ProductSku("300"), "Carrots", "Tasty scala bar", 100), 3),
12 | ProductSku("400") -> ProductStock(Product(ProductSku("400"), "Tomatoes", "Tasty scala bar", 500), 100),
13 | ))
14 |
15 | lazy val inventoryManager = new InventoryManager(initialInventory)(executionContext)
16 | }
17 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/inventory/Product.scala:
--------------------------------------------------------------------------------
1 | package inventory
2 |
3 | import play.api.libs.json._
4 |
5 | case class ProductSku(sku: String)
6 | case class Product(sku: ProductSku, name: String, description: String, weightInGrams: Int)
7 |
8 | object ProductSku {
9 | implicit val productSkuFormat = new Format[ProductSku] {
10 | def writes(o: ProductSku): JsValue = JsString(o.sku)
11 | def reads(json: JsValue): JsResult[ProductSku] = json.validate[String].map(ProductSku(_))
12 | }
13 | }
14 |
15 | object Product {
16 | implicit val productFormat = Json.format[Product]
17 | }
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/shopping/Order.scala:
--------------------------------------------------------------------------------
1 | package shopping
2 |
3 | import java.time.Instant
4 |
5 | import play.api.libs.json.Json
6 |
7 | case class Order(user: String, orderLines: List[OrderLine], instant: Instant)
8 |
9 | object Order {
10 | implicit val orderFormat = Json.format[Order]
11 | }
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/shopping/OrderLine.scala:
--------------------------------------------------------------------------------
1 | package shopping
2 |
3 | import inventory.ProductSku
4 | import play.api.libs.json.Json
5 |
6 | case class OrderLine(product: ProductSku, quantity: Int)
7 |
8 | object OrderLine {
9 | implicit val orderLineFormat = Json.format[OrderLine]
10 | }
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/shopping/Orders.scala:
--------------------------------------------------------------------------------
1 | package shopping
2 |
3 | import scala.collection.immutable.Queue
4 |
5 | case class Orders(orders: Queue[Order]) {
6 | def placeNew(order: Order) = Orders(orders.enqueue(order))
7 | }
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/shopping/Shop.scala:
--------------------------------------------------------------------------------
1 | package shopping
2 |
3 | import java.time.Instant
4 |
5 | import inventory.{InventoryManager, NotEnoughQuantity, SuccessfulAdjustment}
6 | import user.User
7 |
8 | import scala.collection.immutable.Queue
9 | import scala.concurrent.{ExecutionContext, Future}
10 |
11 | class Shop(inventoryManager: InventoryManager)
12 | (implicit executionContext: ExecutionContext) {
13 | @volatile private var orders = Orders(Queue.empty)
14 |
15 | private def addOrder(order: Order): Future[Order] = Future {
16 | this.synchronized {
17 | orders = orders.placeNew(order)
18 |
19 | order
20 | }
21 | }
22 |
23 | def placeOrder(user: User, shoppingCart: ShoppingCart): Future[Order] = {
24 | inventoryManager.applyAdjustment(shoppingCart.toInventoryAdjustment).flatMap {
25 | case SuccessfulAdjustment =>
26 | val order = Order(user.email, shoppingCart.orderLines, Instant.now())
27 |
28 | addOrder(order)
29 | case NotEnoughQuantity => Future.failed(NotEnoughQuantityForOrder)
30 | }
31 | }
32 | }
33 |
34 | case object NotEnoughQuantityForOrder extends Exception
35 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/shopping/ShoppingCart.scala:
--------------------------------------------------------------------------------
1 | package shopping
2 |
3 | import inventory.InventoryAdjustment
4 | import play.api.libs.json.Json
5 |
6 | case class ShoppingCart(orderLines: List[OrderLine] = List.empty) {
7 | def add(orderLine: OrderLine) = {
8 | // TODO: make adding smarter
9 | ShoppingCart(orderLine :: orderLines)
10 | }
11 |
12 | def toInventoryAdjustment = InventoryAdjustment {
13 | orderLines.groupBy(_.product).mapValues(_.map(_.quantity).sum)
14 | }
15 | }
16 |
17 | object ShoppingCart {
18 | implicit val shoppingCartFormat = Json.format[ShoppingCart]
19 | }
20 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/shopping/ShoppingModule.scala:
--------------------------------------------------------------------------------
1 | package shopping
2 |
3 | import inventory.InventoryManager
4 |
5 | import scala.concurrent.ExecutionContext
6 |
7 | trait ShoppingModule {
8 | def executionContext: ExecutionContext
9 | def inventoryManager: InventoryManager
10 |
11 | lazy val shop = new Shop(inventoryManager)(executionContext)
12 | }
13 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/user/RegisteredUsersRepository.scala:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import scala.concurrent.{ExecutionContext, Future}
4 |
5 | class RegisteredUsersRepository(implicit ex: ExecutionContext) {
6 | @volatile private var usersRegistry: UsersRegistry = UsersRegistry()
7 |
8 | def retrieveUser(email: String): Future[Option[User]] = Future { // We use Future just for demonstration
9 | usersRegistry.userFor(email)
10 | }
11 |
12 | def registerUser(user: User): Future[User] = this.synchronized {
13 | if (usersRegistry.contains(user.email)) Future.failed(UserAlreadyExists(user.email))
14 | else {
15 | usersRegistry = usersRegistry.add(user)
16 | Future.successful(user)
17 | }
18 | }
19 |
20 | def deleteUser(email: String): Future[Unit] = Future { // We use Future just for demonstration
21 | this.synchronized {
22 | usersRegistry = usersRegistry.remove(email)
23 | }
24 | }
25 | }
26 |
27 | trait UsersRepositoryException extends Exception
28 | case class UserAlreadyExists(email: String) extends UsersRepositoryException
29 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/user/User.scala:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import play.api.libs.json.Json
4 |
5 | case class User(email: String, name: String, age: Int, password: String)
6 |
7 | object User {
8 | implicit val userFormat = Json.format[User]
9 | }
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/user/UsersModule.scala:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import scala.concurrent.ExecutionContext
4 |
5 | trait UsersModule {
6 | def executionContext: ExecutionContext
7 |
8 | lazy val registeredUsersRepository = new RegisteredUsersRepository()(executionContext)
9 | }
10 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/app/user/UsersRegistry.scala:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | case class UsersRegistry(users: Map[String, User] = Map.empty) {
4 | def userFor(email: String): Option[User] = users.get(email)
5 | def contains(email: String): Boolean = users.contains(email)
6 |
7 | def add(user: User):UsersRegistry = UsersRegistry(users + (user.email -> user))
8 | def remove(email: String): UsersRegistry = UsersRegistry(users - email)
9 | }
10 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/build.sbt:
--------------------------------------------------------------------------------
1 | name := "shopping-app"
2 | version := "0.1"
3 |
4 | lazy val root = (project in file(".")).enablePlugins(PlayScala)
5 |
6 | libraryDependencies ++= Seq(
7 | ws,
8 | "org.scalatestplus.play" %% "scalatestplus-play" % "4.0.2" % Test
9 | )
10 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/conf/application.conf:
--------------------------------------------------------------------------------
1 | play.http.secret.key = "fGGq`JY0KBR?>TVA=dcrBKqV2wMeUXtYh/f_U^nu3]QeB@THW[[J8kq54"
2 |
3 | play.application.loader = ShoppingAppLoader
4 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.6
2 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/shopping-app/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.2")
2 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/web-app/app/WebApp.scala:
--------------------------------------------------------------------------------
1 | import controllers.{ApplicationController, UsersController}
2 | import play.api.ApplicationLoader.Context
3 | import play.api.libs.ws.ahc.AhcWSComponents
4 | import play.api.mvc.{EssentialFilter, Results}
5 | import play.api.routing.Router
6 | import play.api.routing.sird._
7 | import play.api.{Application, ApplicationLoader, BuiltInComponentsFromContext}
8 | import play.filters.HttpFiltersComponents
9 |
10 | class WebAppLoader extends ApplicationLoader {
11 | def load(context: Context): Application = new WebApp(context).application
12 | }
13 |
14 | class WebApp(context: Context)
15 | extends BuiltInComponentsFromContext(context)
16 | with HttpFiltersComponents
17 | with AhcWSComponents {
18 |
19 | override def httpFilters: Seq[EssentialFilter] = Seq.empty // disables filters from HttpFiltersComponents, like CSRF
20 |
21 | lazy val applicationController = new ApplicationController(controllerComponents, wsClient)
22 | lazy val usersController = new UsersController(controllerComponents)
23 |
24 | val mainRoutes: Router.Routes = {
25 | case GET(p"/") => applicationController.index
26 |
27 | case GET(p"/web-page/$domain") => applicationController.retrieveWebPage(s"http://$domain")
28 | }
29 |
30 | val userRoutes: Router.Routes = {
31 | case GET(p"/current-user") => usersController.getUser
32 | case POST(p"/current-user") => usersController.postUser
33 |
34 | case GET(p"/users/$id") => Action {
35 | Results.Ok(id)
36 | }
37 | }
38 |
39 | lazy val router: Router = Router.from(mainRoutes orElse userRoutes)
40 | }
41 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/web-app/app/controllers/ApplicationController.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import play.api.libs.ws.WSClient
4 | import play.api.mvc.{AbstractController, ControllerComponents}
5 |
6 | import scala.concurrent.ExecutionContext
7 | import scala.util.control.NonFatal
8 |
9 | class ApplicationController(cc: ControllerComponents,
10 | wsClient: WSClient)
11 | (implicit ec: ExecutionContext)extends AbstractController(cc) {
12 | // Can be both def or val
13 | def index = Action { request =>
14 | Ok("Hello World!")
15 | }
16 |
17 | def retrieveWebPage(url: String) = Action.async { request =>
18 | wsClient.url(url).get().map { urlResponse =>
19 | Ok(urlResponse.body).as(urlResponse.contentType) // .as changes the content type
20 | } recover {
21 | case NonFatal(e) => ServiceUnavailable(s"Couldn't retrieve $url")
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/web-app/app/controllers/UsersController.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import play.api.libs.json.Json
4 | import play.api.mvc.{AbstractController, ControllerComponents}
5 |
6 | case class User(email: String, age: Int, name: String)
7 |
8 | object User {
9 | // // The machanism we know:
10 | // implicit val userWrites = new Writes[User] {
11 | // def writes(user: User): JsValue = Json.obj(
12 | // "email" -> JsString(user.email),
13 | // "age" -> JsNumber(user.age),
14 | // "name" -> JsString(user.name)
15 | // )
16 | // }
17 |
18 | // Uses a compile time macro instead. Autogenerates code similar to the above, for both writes and reads
19 | implicit val userFormat = Json.format[User]
20 | }
21 |
22 | class UsersController (cc: ControllerComponents) extends AbstractController(cc) {
23 | val getUser = Action { request =>
24 | val user = User("emo@gmail.com", 22, "Emil Dudev")
25 |
26 | // converts user to Json using the Writes typeclass implementation from User.userFormat (or User.userWrites)
27 | Ok(Json.toJson(user))
28 | }
29 |
30 | val postUser = Action(parse.json[User]) { request =>
31 | println(request.body)
32 |
33 | Ok
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/web-app/build.sbt:
--------------------------------------------------------------------------------
1 | name := "web-app"
2 | version := "0.1"
3 |
4 | lazy val root = (project in file(".")).enablePlugins(PlayScala)
5 |
6 | libraryDependencies ++= Seq(
7 | ws, // Web client library, coming from the Play Framework
8 | "org.scalatestplus.play" %% "scalatestplus-play" % "4.0.2" % Test
9 | )
10 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/web-app/conf/application.conf:
--------------------------------------------------------------------------------
1 | play.http.secret.key = "fGGq`JY0KBR?>TVA=dcrBKqV2wMeUXtYh/f_U^nu3]QeB@THW[[J8kq54"
2 |
3 | play.application.loader = WebAppLoader
4 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/web-app/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.6
2 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/web-app/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.2")
2 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/web-client/build.sbt:
--------------------------------------------------------------------------------
1 | name := "web-client"
2 | version := "0.1"
3 |
4 | libraryDependencies ++= Seq(
5 | "com.typesafe.play" %% "play-ahc-ws-standalone" % "2.0.4",
6 | "com.typesafe.play" %% "play-ws-standalone-json" % "2.0.4",
7 |
8 | "org.slf4j" % "slf4j-api" % "1.7.25",
9 | "org.slf4j" % "slf4j-simple" % "1.7.25",
10 |
11 | "org.scalatest" %% "scalatest" % "3.0.5" % "test"
12 | )
13 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/web-client/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.6
2 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/web-client/src/main/scala/webclient/UserPoster.scala:
--------------------------------------------------------------------------------
1 | package webclient
2 |
3 | import play.api.libs.json.{JsSuccess, JsValue, Json}
4 | import play.api.libs.ws.ahc.StandaloneAhcWSClient
5 |
6 | import scala.concurrent.{ExecutionContext, Future}
7 |
8 | // implicits that tell the client how to write JSON as an HTTP body
9 | import play.api.libs.ws.JsonBodyWritables._
10 | // Allows response.body[JsValue]
11 | import play.api.libs.ws.JsonBodyReadables._
12 |
13 | case class User(email: String, age: Int, name: String)
14 | object User {
15 | // // The machanism we know:
16 | // implicit val userWrites = new Writes[User] {
17 | // def writes(user: User): JsValue = Json.obj(
18 | // "email" -> JsString(user.email),
19 | // "age" -> JsNumber(user.age),
20 | // "name" -> JsString(user.name)
21 | // )
22 | // }
23 |
24 | // Uses a compile time macro instead. Autogenerates code similar to the above, for both writes and reads
25 | implicit val userFormat = Json.format[User]
26 | }
27 |
28 | class UserPoster(wsClient: StandaloneAhcWSClient)
29 | (implicit ec: ExecutionContext) {
30 | def post(email: String, age: Int, name: String): Future[User] = {
31 | val user = User(email, age, name)
32 |
33 | wsClient.url("https://postman-echo.com/post") // postman-echo is just a testing server, that generates json
34 | .post(Json.toJson(user)) // Serialize user to JSON using User.userFormat
35 | .map(_.body[JsValue] \ "data") // postman-echo returns a JSON with the original user in a "data" field
36 | .map(_.validate[User]) // Deserialize JSON to User using User.userFormat. Can be either JsSuccess or JsError
37 | .collect {
38 | case JsSuccess(receivedUser, _) => receivedUser
39 | case _ => throw new RuntimeException("unexpected")
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lectures/examples/14-modularity-and-web/web-client/src/main/scala/webclient/WebClientApp.scala:
--------------------------------------------------------------------------------
1 | package webclient
2 |
3 | import akka.actor.ActorSystem
4 | import akka.stream.ActorMaterializer
5 | import play.api.libs.ws.ahc.StandaloneAhcWSClient
6 |
7 | import scala.concurrent.duration.Duration
8 | import scala.concurrent.{Await, ExecutionContext}
9 | import scala.util.Success
10 |
11 | object WebClientApp {
12 | implicit val system = ActorSystem() // an actor system and materializer are required for advenced features
13 | implicit val materializer = ActorMaterializer() // even if they are not used
14 | implicit val ec: ExecutionContext = system.dispatcher // every actor system comes with an execution context we can use
15 |
16 | val wsClient = StandaloneAhcWSClient()
17 |
18 | val userPoster = new UserPoster(wsClient)
19 |
20 | def main(args: Array[String]): Unit = {
21 | val posting = userPoster.post("zstoychev@gmail.com", 31, "Zdravko")
22 | .andThen { case Success(user) => println(s"User: $user") }
23 | .andThen { case _ => terminate() } // No matter if it's successs or error, terminate
24 | }
25 |
26 | // terminates resources and thread pools
27 | def terminate(): Unit = {
28 | wsClient.close()
29 | Await.result(system.terminate(), Duration.Inf)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lectures/generate-presentation.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if [ "$#" -ne 1 ]; then
4 | echo "Usage: ./generate-presentation.sh "
5 | exit
6 | fi
7 |
8 | lecture_file="$1"
9 | lecture="${lecture_file%.*}"
10 |
11 | pandoc -t revealjs \
12 | -s \
13 | -o "$lecture".html \
14 | "$lecture_file" \
15 | -V revealjs-url=reveal-js \
16 | -V theme=white \
17 | --css=theme/theme.css \
18 | -V transition=fade \
19 | -V center=false
20 |
--------------------------------------------------------------------------------
/lectures/images/01-intro/essential-scala.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/01-intro/essential-scala.png
--------------------------------------------------------------------------------
/lectures/images/01-intro/functional-programming-in-scala.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/01-intro/functional-programming-in-scala.jpg
--------------------------------------------------------------------------------
/lectures/images/01-intro/functional.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/01-intro/functional.jpg
--------------------------------------------------------------------------------
/lectures/images/01-intro/grammar-size.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/01-intro/grammar-size.png
--------------------------------------------------------------------------------
/lectures/images/01-intro/programming-in-scala.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/01-intro/programming-in-scala.png
--------------------------------------------------------------------------------
/lectures/images/01-intro/types.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/01-intro/types.jpeg
--------------------------------------------------------------------------------
/lectures/images/02-scala-intro/scalastic_principles.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/02-scala-intro/scalastic_principles.jpeg
--------------------------------------------------------------------------------
/lectures/images/03-oop/construction.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/03-oop/construction.jpg
--------------------------------------------------------------------------------
/lectures/images/03-oop/enlightenment.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/03-oop/enlightenment.jpg
--------------------------------------------------------------------------------
/lectures/images/03-oop/last-time.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/03-oop/last-time.webp
--------------------------------------------------------------------------------
/lectures/images/04-functional-programming-basics/Purely_functional_tree_after.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 | ]>
6 |
8 |
9 |
144 |
--------------------------------------------------------------------------------
/lectures/images/04-functional-programming-basics/filter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/04-functional-programming-basics/filter.png
--------------------------------------------------------------------------------
/lectures/images/04-functional-programming-basics/function.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/04-functional-programming-basics/function.png
--------------------------------------------------------------------------------
/lectures/images/04-functional-programming-basics/godji-opakovka2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/04-functional-programming-basics/godji-opakovka2.jpg
--------------------------------------------------------------------------------
/lectures/images/04-functional-programming-basics/map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/04-functional-programming-basics/map.png
--------------------------------------------------------------------------------
/lectures/images/04-functional-programming-basics/stack.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/04-functional-programming-basics/stack.jpg
--------------------------------------------------------------------------------
/lectures/images/05-currying-tuples-collections/HaskellCurry.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/05-currying-tuples-collections/HaskellCurry.jpg
--------------------------------------------------------------------------------
/lectures/images/05-currying-tuples-collections/Left-fold-transformation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/05-currying-tuples-collections/Left-fold-transformation.png
--------------------------------------------------------------------------------
/lectures/images/05-currying-tuples-collections/Right-fold-transformation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/05-currying-tuples-collections/Right-fold-transformation.png
--------------------------------------------------------------------------------
/lectures/images/05-currying-tuples-collections/chicken-curry.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/05-currying-tuples-collections/chicken-curry.jpg
--------------------------------------------------------------------------------
/lectures/images/05-currying-tuples-collections/reduce.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/05-currying-tuples-collections/reduce.png
--------------------------------------------------------------------------------
/lectures/images/05-currying-tuples-collections/work.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/05-currying-tuples-collections/work.png
--------------------------------------------------------------------------------
/lectures/images/05-currying-tuples-collections/zipper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/05-currying-tuples-collections/zipper.png
--------------------------------------------------------------------------------
/lectures/images/11-type-classes/cats-cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/11-type-classes/cats-cat.png
--------------------------------------------------------------------------------
/lectures/images/11-type-classes/cats-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/11-type-classes/cats-small.png
--------------------------------------------------------------------------------
/lectures/images/11-type-classes/cats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/11-type-classes/cats.png
--------------------------------------------------------------------------------
/lectures/images/11-type-classes/scala-with-cats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/11-type-classes/scala-with-cats.png
--------------------------------------------------------------------------------
/lectures/images/12-monads-and-functors/47271389-8eea0900-d581-11e8-8e81-5b932e336336.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/12-monads-and-functors/47271389-8eea0900-d581-11e8-8e81-5b932e336336.png
--------------------------------------------------------------------------------
/lectures/images/12-monads-and-functors/functional-programming-in-scala.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/12-monads-and-functors/functional-programming-in-scala.jpg
--------------------------------------------------------------------------------
/lectures/images/14-modularity-and-web/04fig02_alt.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/14-modularity-and-web/04fig02_alt.jpg
--------------------------------------------------------------------------------
/lectures/images/14-modularity-and-web/04fig03_alt.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scala-fmi/scala-fmi-2019/6acafad6b1c47f884f99cf1c911461b551300f51/lectures/images/14-modularity-and-web/04fig03_alt.jpg
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/project-instructions.md:
--------------------------------------------------------------------------------
1 | Дойде време за финалната част на курса, а именно финалните проекти.
2 |
3 | По проект може да работите самостоятелно или в екип от двама. Съответно ако сте двама, вашият проект трябва да е малко по-голям.
4 |
5 | # Предложение за проект
6 |
7 | Пишете в #projects канала на Slack или в Moodle с предложението ви за проект. Ние ще одобрим вашата тема и/или ще ви дадем допълнителни носоки, ако има нужда от изглаждане или допълнителни идеи.
8 |
9 | # Обхват на проекта
10 |
11 | Основното изискване към темата на проекта е да описва определена цялостна функционалност под формата на приложение или библиотека. Няма фиксирано изискване за размера на проекта, но като насока може да приемете големина колкото приблизително 3–4 домашни за един човек, или 6–8 домашни за екип от двама. След вашето предложение на тема ние ще ви насочим дали то е твърде голямо или дали има нужда да добавите още функционалност.
12 |
13 | Тъй като ефектите и абстракциите върху тях бяха една голяма част от курса, като техническо изискване ще поставим вашето решение да борави с няколко ефекта в себе си (разбира се, само където това би било наистина необходимо). С това искаме от вас страничните ефекти да са сведени до минимум и да бъдат заменени от функционални ефекти.
14 |
15 | Може да използвате всякакви библиотеки, които да ви помогнат. Попитайте ни за насоки, ако се интересувате от библиотеки за определени цели. Тук ще опитаме да поддържаме списък от полезни такива.
16 |
17 | Едни от основните цели на проекта са да се сблъсквате със създаването на нещо цялостно и практично на Scala във функционален стил, да се упражните върху практически проблеми, които се срещат в реална обстановка, да помислите сами за дизайна на нов проект, и да реализирате нещо, което да ви е приятно. Затова помислете за тема, която винаги сте искали да опитате да реализирате, или ако нямате ясна идея, помислете за част от функицоналността на вече нещо съществуващо, която да напишете на Scala.
18 |
19 | # Защита на проект
20 |
21 | Финалната стъпка при проекта е неговата защита, за която ще имаме две дати – по средата и в края на сесията. На защитата трябва да ни представите вашата реализация и да можете да се аргументирате за решенията, които сте взели. При екипен проект от двама трябва да е сравнително ясно кой за какво е бил отговорен, за да можем да оценим всеки от двама ви.
22 |
23 | Препоръчваме ви да си създадете git хранилище, което да споделите с нас достатъчно рано преди самата защита, за да можем да следим прогреса ви по проекта.
24 |
25 | # Оценяване
26 |
27 | Финалният проект ви носи 50 точки. Точките се разпределят по следния начин:
28 |
29 | * 22,5 точки за функционалност и коректност на проекта. Тази оценка ще е според големината на проекта ви и това доколко успешно сте реализирали функционалността на вашето предложение за тема.
30 | * 22,5 точки за стил и дизайн на решението. Ще следим доколко кодът е функционален, как успява да ограничи страничните ефекти до правилните места, и какъв дизайн сте създали за реализацията на вашия проект.
31 | * 5 точки за тестове. Ще изискваме да покриват поне основната функционалност на вашия проект.
32 | * Бонус точки за добри решения или изключителни проекти
33 |
--------------------------------------------------------------------------------