├── .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 | ![](images/01-intro/functional.jpg) 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 | ![](images/01-intro/types.jpeg){ 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 | ![](images/01-intro/grammar-size.png){ 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 | ![](images/01-intro/programming-in-scala.png){ height="320" } 263 | ![](images/01-intro/functional-programming-in-scala.jpg){ height="320" } 264 | ![](images/01-intro/essential-scala.png){ 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 | ![](images/02-scala-intro/scalastic_principles.jpeg){ 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 | ![](images/02-scala-intro/scala_type_hierarchy.svg) 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 | 11 | 12 | pf_tree_after 13 | 14 | 15 | xs 16 | xs 17 | 18 | 19 | d 20 | 21 | d 22 | 23 | 24 | xs->d 25 | 26 | 27 | 28 | 29 | b 30 | 31 | b 32 | 33 | 34 | d->b 35 | 36 | 37 | 38 | 39 | g 40 | 41 | g 42 | 43 | 44 | d->g 45 | 46 | 47 | 48 | 49 | a 50 | 51 | a 52 | 53 | 54 | b->a 55 | 56 | 57 | 58 | 59 | c 60 | 61 | c 62 | 63 | 64 | b->c 65 | 66 | 67 | 68 | 69 | f 70 | 71 | f 72 | 73 | 74 | g->f 75 | 76 | 77 | 78 | 79 | h 80 | 81 | h 82 | 83 | 84 | g->h 85 | 86 | 87 | 88 | 89 | ys 90 | ys 91 | 92 | 93 | d' 94 | 95 | d' 96 | 97 | 98 | ys->d' 99 | 100 | 101 | 102 | 103 | d'->b 104 | 105 | 106 | 107 | 108 | g' 109 | 110 | g' 111 | 112 | 113 | d'->g' 114 | 115 | 116 | 117 | 118 | g'->h 119 | 120 | 121 | 122 | 123 | f' 124 | 125 | f' 126 | 127 | 128 | g'->f' 129 | 130 | 131 | 132 | 133 | e 134 | 135 | e 136 | 137 | 138 | f'->e 139 | 140 | 141 | 142 | 143 | 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 | --------------------------------------------------------------------------------