├── project ├── build.properties └── plugins.sbt ├── .gitignore ├── src └── main │ └── scala │ └── lambda │ ├── stream │ ├── data │ │ └── Main.scala │ └── basics.scala │ ├── io │ ├── priority │ │ ├── HighLowPriorityRunner.scala │ │ └── Main.scala │ └── basics.scala │ └── intro.scala ├── README.md └── fahrenheit.txt /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.7.1 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.4.1") 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # editors specific 2 | *.swp 3 | *~* 4 | *#* 5 | .cache-main 6 | .classpath 7 | .project 8 | .settings/ 9 | .*.md.html 10 | 11 | # scala/java specific 12 | *.class 13 | *.log 14 | 15 | # sbt/mvn specific 16 | .cache 17 | .history 18 | .lib/ 19 | dist/* 20 | target/ 21 | lib_managed/ 22 | src_managed/ 23 | project/boot/ 24 | project/plugins/project/ 25 | /bin/ 26 | -------------------------------------------------------------------------------- /src/main/scala/lambda/stream/data/Main.scala: -------------------------------------------------------------------------------- 1 | package lambda.stream.data 2 | 3 | import cats.effect.{IO, IOApp} 4 | import cats.syntax.all._ 5 | import fs2.io.file.{Files, Path} 6 | 7 | object Main extends IOApp.Simple { 8 | def fahrenheitToCelsius(f: Double): Double = 9 | (f - 32.0) * (5.0 / 9.0) 10 | 11 | override final val run: IO[Unit] = 12 | Files[IO] 13 | .readAll(Path("fahrenheit.txt")) 14 | .through(fs2.text.utf8.decode) 15 | .through(fs2.text.lines) 16 | .tail 17 | .mapFilter(line => line.toDoubleOption) 18 | .map(fahrenheitToCelsius) 19 | .map(celsius => celsius.toString) 20 | .intersperse("\n") 21 | .through(fs2.text.utf8.encode) 22 | .through(Files[IO].writeAll(Path("celsius.txt"))) 23 | .compile 24 | .drain 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/lambda/io/priority/HighLowPriorityRunner.scala: -------------------------------------------------------------------------------- 1 | package lambda.io.priority 2 | 3 | import cats.effect.IO 4 | import cats.effect.std.{Queue, Semaphore, Supervisor} 5 | import scala.concurrent.duration._ 6 | 7 | object HighLowPriorityRunner { 8 | final case class Config( 9 | highPriorityJobs: Queue[IO, IO[Unit]], 10 | lowPriorityJobs: Queue[IO, IO[Unit]], 11 | rateLimiter: Semaphore[IO] 12 | ) 13 | 14 | def apply(config: Config): IO[Unit] = 15 | Supervisor[IO].use { supervisor => 16 | val nextJob = 17 | config.highPriorityJobs.tryTake.flatMap { 18 | case Some(hpJob) => hpJob 19 | case None => config.lowPriorityJobs.tryTake.flatMap { 20 | case Some(lpJob) => lpJob 21 | case None => IO.sleep(100.millis) 22 | } 23 | } 24 | 25 | val processOneJob = 26 | config.rateLimiter.acquire >> 27 | supervisor.supervise(nextJob.guarantee(config.rateLimiter.release)) 28 | 29 | processOneJob.foreverM.void 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/lambda/io/priority/Main.scala: -------------------------------------------------------------------------------- 1 | package lambda.io.priority 2 | 3 | import cats.effect.{IO, IOApp} 4 | import cats.effect.std.{Queue, Semaphore} 5 | import cats.syntax.all._ 6 | import scala.concurrent.duration._ 7 | 8 | object Main extends IOApp.Simple { 9 | private def createJob(id: Int, highPriority: Boolean = true): IO[Unit] = 10 | IO.println(s"Starting (${if (highPriority) "High" else "Low"} priority) job ${id}") >> 11 | IO.sleep(1.second) >> 12 | IO.println(s"Finished job ${id}!") 13 | 14 | override final val run: IO[Unit] = 15 | ( 16 | Queue.unbounded[IO, IO[Unit]], 17 | Queue.unbounded[IO, IO[Unit]], 18 | Semaphore[IO](n = 2) 19 | ).mapN(HighLowPriorityRunner.Config.apply).flatMap { config => 20 | HighLowPriorityRunner(config).background.surround { 21 | List.range(0, 10).traverse_(id => config.lowPriorityJobs.offer(createJob(id, highPriority = false))) >> 22 | IO.sleep(100.millis) >> 23 | List.range(10, 15).traverse_(id => config.highPriorityJobs.offer(createJob(id))) >> 24 | IO.sleep(4.seconds) >> 25 | List.range(15, 20).traverse_(id => config.highPriorityJobs.offer(createJob(id))) >> 26 | IO.sleep(4.seconds) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/lambda/intro.scala: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | sealed trait Program[+A] { 4 | import Program.{Continue, Halt} 5 | 6 | def transformOutput[B](f: A => B): Program[B] = this match { 7 | case Continue(value) => Continue(f(value)) 8 | case Halt => Halt 9 | } 10 | 11 | def zip[B](that: Program[B]): Program[(A, B)] = this match { 12 | case Continue(a) => that.transformOutput(b => (a, b)) 13 | case Halt => Halt 14 | } 15 | 16 | def chainProgram[B](f: A => Program[B]): Program[B] = this match { 17 | case Continue(value) => f(value) 18 | case Halt => Halt 19 | } 20 | 21 | def recover[B >: A](fallback: Program[B]): Program[B] = this match { 22 | case Continue(value) => Continue(value) 23 | case Halt => fallback 24 | } 25 | 26 | def resultOr[B >: A](default: B): B = this match { 27 | case Continue(value) => value 28 | case Halt => default 29 | } 30 | } 31 | 32 | object Program { 33 | private final case class Continue[+A](value: A) extends Program[A] 34 | private final case object Halt extends Program[Nothing] 35 | 36 | def continue[A](value: A): Program[A] = Continue(value) 37 | def halt: Program[Nothing] = Halt 38 | } 39 | 40 | object Example { 41 | def num(n: Int): Program[Int] = 42 | Program.continue(n) 43 | 44 | def duplicate(p: Program[Int]): Program[Int] = 45 | p.transformOutput(n => n * 2) 46 | 47 | def divide(p1: Program[Int], p2: Program[Int]): Program[Int] = 48 | p1.zip(p2).chainProgram { 49 | case (n1, n2) => 50 | if (n2 != 0) Program.continue(n1 / n2) 51 | else Program.halt 52 | } 53 | 54 | def main(args: Array[String]): Unit = { 55 | // ((x / y) / z) * 2 56 | def equation(x: Int, y: Int, z: Int): Int = 57 | duplicate(divide( 58 | divide(num(x), num(y)), 59 | num(z) 60 | )).resultOr(-1) 61 | 62 | println(s"The result is: ${equation(100, 10, 5)}") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/scala/lambda/io/basics.scala: -------------------------------------------------------------------------------- 1 | package lambda.io 2 | 3 | import cats.effect.{IO, IOApp, Resource} 4 | import cats.syntax.all._ 5 | import scala.concurrent.duration._ // Provides duration units. 6 | import scala.io.Source 7 | 8 | /** Program 1: Sequential composition. */ 9 | object Basics1_1 extends IOApp.Simple { 10 | val myIO: IO[Unit] = 11 | IO.println("Hello, World!") 12 | 13 | override final val run: IO[Unit] = 14 | myIO.flatMap(_ => myIO) 15 | } 16 | 17 | object Basics1_2 extends IOApp.Simple { 18 | val myIO: IO[Unit] = 19 | IO.println("Hello, World!") 20 | 21 | override final val run: IO[Unit] = 22 | myIO >> myIO >> myIO >> myIO >> myIO 23 | } 24 | // ---------------------------------------------- 25 | 26 | 27 | /** Program 2: Async & Cancellable operations. */ 28 | object Basics2 extends IOApp.Simple { 29 | val tick: IO[Unit] = 30 | (IO.sleep(1.second) >> IO.println("Tick")).foreverM 31 | 32 | val cancelToken: IO[Unit] = 33 | IO.readLine.void 34 | 35 | override final val run: IO[Unit] = 36 | tick.start.flatMap { tickFiber => 37 | cancelToken >> tickFiber.cancel 38 | } 39 | } 40 | // ---------------------------------------------- 41 | 42 | 43 | /** Program 3: Parallel operations. */ 44 | object Basics3_1 extends IOApp.Simple { 45 | val ioA: IO[Int] = 46 | IO.sleep(1.second) >> IO.println("Running ioA").as(1) 47 | 48 | val ioB: IO[String] = 49 | IO.sleep(1.second) >> IO.println("Running ioB").as("Balmung") 50 | 51 | val ioC: IO[Boolean] = 52 | IO.sleep(1.second) >> IO.println("Running ioC").as(true) 53 | 54 | override final val run: IO[Unit] = 55 | (ioA, ioB, ioC).parTupled.flatMap { 56 | case (a, b, c) => 57 | IO.println(s"a: ${a} | b: ${b} | c: ${c}") 58 | } 59 | } 60 | 61 | object Basics3_2 extends IOApp.Simple { 62 | override final val run: IO[Unit] = 63 | List.range(start = 0, end = 11).parTraverse { i => 64 | IO.sleep(1.second) >> IO.println(i) >> IO.delay((i * 10) + 5) 65 | } flatMap { data => 66 | IO.println(s"Final data: ${data}") 67 | } 68 | } 69 | // ---------------------------------------------- 70 | 71 | 72 | /** Program 4: Error handling and resource management. */ 73 | object Basics4 extends IOApp.Simple { 74 | def fahrenheitToCelsius(f: Double): Double = 75 | (f - 32.0) * (5.0 / 9.0) 76 | 77 | def process(lines: List[String]): List[Double] = 78 | lines.filter { line => 79 | !line.trim.isEmpty && !line.startsWith("//") 80 | } map { line => 81 | fahrenheitToCelsius(f = line.toDouble) 82 | } 83 | 84 | override final val run: IO[Unit] = 85 | Resource 86 | .fromAutoCloseable(IO(Source.fromFile("fahrenheit.txt"))) 87 | .use(file => IO(file.getLines().toList)) 88 | .map(process) 89 | .attempt 90 | .flatMap { 91 | case Right(data) => 92 | IO.println(s"Output: ${data.take(5).mkString("[", ", ", ", ...]")}") 93 | 94 | case Left(ex) => 95 | IO.println(s"Error: ${ex.getMessage}") 96 | } 97 | } 98 | // ---------------------------------------------- 99 | -------------------------------------------------------------------------------- /src/main/scala/lambda/stream/basics.scala: -------------------------------------------------------------------------------- 1 | package lambda.stream 2 | 3 | import cats.effect.{IO, IOApp, Ref, Resource} 4 | import cats.effect.std.Random 5 | import fs2.Stream 6 | import scala.concurrent.duration._ 7 | 8 | /** Program 1: Infinite data. */ 9 | object Basics1_1 extends IOApp.Simple { 10 | override final val run: IO[Unit] = 11 | Stream(1, 2, 3) 12 | .covary[IO] 13 | .compile 14 | .toList 15 | .flatMap(IO.println) 16 | } 17 | 18 | object Basics1_2 extends IOApp.Simple { 19 | override final val run: IO[Unit] = 20 | Stream 21 | .iterate(0)(x => x + 1) 22 | .evalTap(IO.println) 23 | .take(10) 24 | .compile 25 | .toList 26 | .flatMap(IO.println) 27 | } 28 | 29 | object Basics1_3 extends IOApp.Simple { 30 | override final val run: IO[Unit] = 31 | Random.scalaUtilRandomSeedInt[IO](3).flatMap { random => 32 | Stream 33 | .repeatEval(random.nextInt) 34 | .take(10) 35 | .compile 36 | .toList 37 | .flatMap(IO.println) 38 | } 39 | } 40 | // ---------------------------------------------- 41 | 42 | 43 | /** Program 2: Resource and effects management. */ 44 | object Basics2 extends IOApp.Simple { 45 | final class DBService { 46 | val streamAllData: Stream[IO, Int] = 47 | Stream.range(start = 0, stopExclusive = 21, step = 2) 48 | } 49 | object DBService { 50 | final val instance: Resource[IO, DBService] = 51 | Resource.make( 52 | IO.println("Connecting to the database").as(new DBService) 53 | )(_ => 54 | IO.println("Closing the database connection") 55 | ) 56 | } 57 | 58 | override final val run: IO[Unit] = 59 | Stream.resource(DBService.instance).flatMap { db => 60 | Stream.eval(IO.println("Before query the db")) ++ 61 | db.streamAllData.evalMap(IO.println) ++ 62 | Stream.eval(IO.println("After query the db")) 63 | }.compile.drain 64 | } 65 | // ---------------------------------------------- 66 | 67 | 68 | /** Program 3: Error handling. */ 69 | object Basics3 extends IOApp.Simple { 70 | final case object Failure extends Throwable("Faliure") 71 | 72 | override final val run: IO[Unit] = 73 | Stream(1, 2, 3).evalMap { 74 | case 1 => IO.pure("Foo") 75 | case 2 => IO.raiseError(Failure) 76 | case _ => IO.raiseError(new Exception("Fatal")) 77 | }.handleErrorWith { 78 | case Failure => Stream("Bar", "Baz") 79 | case fatal => Stream.exec(IO.println(s"Fatal error ${fatal}")) 80 | }.evalMap(IO.println).compile.drain 81 | } 82 | // ---------------------------------------------- 83 | 84 | 85 | /** Program 4: Control flow & concurrency. */ 86 | object Basics4 extends IOApp.Simple { 87 | def greetStream(nameRef: Ref[IO, String]): Stream[IO, Nothing] = 88 | Stream.fixedDelay[IO](1.second).foreach { _ => 89 | nameRef.get.flatMap { name => 90 | IO.println(s"Hello ${name}!") 91 | } 92 | } 93 | 94 | def updatesStream(nameRef: Ref[IO, String]): Stream[IO, Unit] = 95 | Stream.repeatEval(IO.readLine).evalMap { line => 96 | if (line.isBlank) IO.none[Unit] 97 | else nameRef.set(line.trim).option 98 | }.unNoneTerminate 99 | 100 | override final val run: IO[Unit] = 101 | IO.ref("Luis").flatMap { nameRef => 102 | updatesStream(nameRef) 103 | .concurrently(greetStream(nameRef)) 104 | .compile 105 | .drain 106 | } 107 | } 108 | // ---------------------------------------------- 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Programs as Values 2 | 3 | Source code of the _"Programs as Values: The foundations of next-gen concurrent programming"_ tech talk. 4 | 5 | ## Links 6 | 7 | + **Slides**: https://perficient-my.sharepoint.com/:p:/p/luis_mejias/EUqvic_6kUJCr8q0ogTRjp8BCBROZstVkaosgd1WlEIPjA 8 | + **Talk recording** _(in Spanish)_: https://www.youtube.com/watch?v=iJ5jmMeAZwE&t=5652s&ab_channel=Josekell 9 | + **Programs as Values series** _(Fabio Labella)_: https://systemfw.org/archive.html 10 | + **The case for effect systems** _(Daniel Spiewak)_: https://www.youtube.com/watch?v=qgfCmQ-2tW0 11 | + **Streams - Your New Favorite Primitive** _(Ryan Peters)_: https://www.youtube.com/watch?v=BZ8O6T7Y1UE 12 | + **Functional Programming with Effects** _(Rob Norris)_: https://www.youtube.com/watch?v=30q6BkBv5MY&ab_channel=ScalaDaysConferences 13 | + **What is an Effect?** _(Adam Rosien)_: https://www.inner-product.com/posts/what-is-an-effect/ 14 | + **Why FP** _(Luis Miguel Mejía Suárez)_: https://gist.github.com/BalmungSan/bdb163a080af54d3713e9e7c4a37ff51 15 | 16 | ## Extras 17 | 18 | ### Referential Transparency 19 | 20 | Referential transparency is a property of expressions, 21 | which dictates that you can always replace a variable with the expression it refers, 22 | without altering in any way the behaviour of the program.
23 | In the same way, you can always give a name to any expression, 24 | and use this new variable in all the places where the same expression was used; 25 | and, again, the behaviour of the program must remain the same. 26 | 27 | Let's see in practice what does that means: 28 | 29 | ```scala 30 | val data = List(1, 2, 3) 31 | val first = data.head 32 | val result = first + first 33 | println(result) 34 | ``` 35 | 36 | This little program will print `2` since `first` refers to the `head` of `data`; which is `1`
37 | Now, let's see what happens if we replace the `first` variable with its expression. 38 | 39 | ```scala 40 | val data = List(1, 2, 3) 41 | val result = data.head + data.head 42 | println(result) 43 | ``` 44 | 45 | The result will be the same since `data.head` will always return `1`
46 | Thus, we can say that `List#head` is referentially transparent. 47 | 48 | Let's now see and example that breaks the property: 49 | 50 | ```scala 51 | val data = List(1, 2, 3).iterator 52 | val first = data.next() 53 | val result = first + first 54 | println(result) 55 | ``` 56 | 57 | Again, the above program will print `2`; 58 | because, `first` will evaluate to `data.next()`, which on its first call will return `1`
59 | But, this time the result will change if we replace `first` with the expression it refers to: 60 | 61 | ```scala 62 | val data = List(1, 2, 3).iterator 63 | val result = data.next() + data.next() 64 | println(result) 65 | ``` 66 | 67 | In this case, the program will print `3`; 68 | because, the first `data.next()` will return `1` but the second call will return `2`, so `result` will be `3`
69 | As such, we can say that `Iterator#next()` is NOT referentially transparent. 70 | 71 | > **Note**: Expressions that satisfy this property will be called _"values"_.
72 | > Additionally, if a program is made up entirely of referentially transparent expressions _(values)_, 73 | > then you may evaluate it using the _"substitution model"_. 74 | 75 | ### Composition 76 | 77 | Composition is a property of systems, 78 | where you can build complex systems by composing simpler ones. 79 | In consequence, it also means that you can understand complex systems 80 | by understating its parts and the way the compose. 81 | 82 | > **Note**: Composition is not a binary attribute like Referential Transparency, but rather an spectrum; 83 | > the more compositional our programs are then they will be easier to refactor. 84 | 85 | ### Monads 86 | 87 | The (_in_)famous _"M"_ word, 88 | Monads are a mechanism used to solve a fundamental problem that you will find 89 | when using the _"Programs as Values"_ paradigm.
90 | To understand them, let's first understand the problem. 91 | 92 | When our programs only manipulate plain values using functions _(which are also values)_, 93 | it is very simple to compose those functions together into bigger ones.
94 | For example, if we had a function `f: A => B` and a function: `g: B => C` 95 | then creating a function `h: A => C` is as simple as `h = a => g(f(a))` 96 | 97 | However, what happens when we now have effectual values like `Option[A]` or `IO[A]`, 98 | and effectual functions like `f: A => F[B]` and `g: B => F[C]`; 99 | we can not longer use traditional function composition to create `h: A => F[C]`
100 | Although, we can do this: 101 | 102 | ```scala 103 | val h: A => F[C] = { a: A => 104 | f(a).flatMap(g) 105 | } 106 | ``` 107 | 108 | Nevertheless, this requires the assumption that such `flatMap` function exists and has the type signature we want, 109 | that is what Monads are, a Monad is just a triplet of a: 110 | + A type constructor _(`F[_]`)_ 111 | + A `flatMap(fa: F[A])(f: A => F[B]): F[B]` implementation for that type constructor _(and also `pure(a: A): F[A]`)_ 112 | + A proof that such implementation satisfies some laws 113 | 114 | More importantly, such laws guarantee that such `flatMap` function somehow represents the concept of sequence. 115 | Meaning that for `IO` `flatMap` always means do this and then do that, just like a `;` on imperative languages. 116 | -------------------------------------------------------------------------------- /fahrenheit.txt: -------------------------------------------------------------------------------- 1 | // this file contains a list of temperatures in degrees fahrenheit 2 | 18.0 3 | 17.9 4 | 25.3 5 | 34.2 6 | 55.3 7 | 44.2 8 | 22.0 9 | 19.0 10 | 44.7 11 | 33.2 12 | 55.7 13 | 66 14 | 78 15 | 72 16 | 64.0 17 | 99 18 | 18.0 19 | 17.9 20 | 25.3 21 | 34.2 22 | 55.3 23 | 44.2 24 | 22.0 25 | 19.0 26 | 44.7 27 | 33.2 28 | 55.7 29 | 66 30 | 78 31 | 72 32 | 64.0 33 | 99 34 | 18.0 35 | 17.9 36 | 25.3 37 | 34.2 38 | 55.3 39 | 44.2 40 | 22.0 41 | 18.0 42 | 17.9 43 | 25.3 44 | 34.2 45 | 55.3 46 | 44.2 47 | 22.0 48 | 19.0 49 | 44.7 50 | 33.2 51 | 55.7 52 | 66 53 | 78 54 | 72 55 | 64.0 56 | 99 57 | 18.0 58 | 17.9 59 | 18.0 60 | 17.9 61 | 25.3 62 | 34.2 63 | 55.3 64 | 44.2 65 | 22.0 66 | 19.0 67 | 44.7 68 | 33.2 69 | 55.7 70 | 66 71 | 78 72 | 72 73 | 64.0 74 | 99 75 | 18.0 76 | 17.9 77 | 25.3 78 | 34.2 79 | 55.3 80 | 44.2 81 | 22.0 82 | 19.0 83 | 44.7 84 | 33.2 85 | 55.7 86 | 66 87 | 78 88 | 72 89 | 64.0 90 | 99 91 | 18.0 92 | 17.9 93 | 25.3 94 | 34.2 95 | 55.3 96 | 44.2 97 | 22.0 98 | 19.0 99 | 44.7 100 | 33.2 101 | 55.7 102 | 66 103 | 78 104 | 72 105 | 64.0 106 | 99 107 | 18.0 108 | 17.9 109 | 25.3 110 | 34.2 111 | 55.3 112 | 44.2 113 | 22.0 114 | 19.0 115 | 44.7 116 | 33.2 117 | 55.7 118 | 66 119 | 78 120 | 72 121 | 64.0 122 | 18.0 123 | 17.9 124 | 25.3 125 | 34.2 126 | 55.3 127 | 44.2 128 | 22.0 129 | 19.0 130 | 44.7 131 | 33.2 132 | 55.7 133 | 66 134 | 78 135 | 72 136 | 64.0 137 | 99 138 | 18.0 139 | 17.9 140 | 25.3 141 | 34.2 142 | 55.3 143 | 44.2 144 | 22.0 145 | 19.0 146 | 44.7 147 | 33.2 148 | 55.7 149 | 66 150 | 78 151 | 72 152 | 64.0 153 | 99 154 | 18.0 155 | 17.9 156 | 25.3 157 | 34.2 158 | 55.3 159 | 44.2 160 | 22.0 161 | 19.0 162 | 44.7 163 | 33.2 164 | 55.7 165 | 66 166 | 78 167 | 72 168 | 64.0 169 | 99 170 | 18.0 171 | 17.9 172 | 25.3 173 | 34.2 174 | 55.3 175 | 44.2 176 | 22.0 177 | 19.0 178 | 44.7 179 | 33.2 180 | 55.7 181 | 66 182 | 78 183 | 72 184 | 64.0 185 | 99 186 | 18.0 187 | 17.9 188 | 25.3 189 | 34.2 190 | 55.3 191 | 44.2 192 | 22.0 193 | 19.0 194 | 44.7 195 | 33.2 196 | 55.7 197 | 66 198 | 78 199 | 72 200 | 64.0 201 | 99 202 | 99 203 | 18.0 204 | 17.9 205 | 25.3 206 | 34.2 207 | 55.3 208 | 44.2 209 | 22.0 210 | 19.0 211 | 44.7 212 | 33.2 213 | 55.7 214 | 66 215 | 78 216 | 72 217 | 64.0 218 | 99 219 | 25.3 220 | 34.2 221 | 55.3 222 | 44.2 223 | 22.0 224 | 19.0 225 | 44.7 226 | 33.2 227 | 55.7 228 | 66 229 | 78 230 | 72 231 | 64.0 232 | 99 233 | 18.0 234 | 17.9 235 | 25.3 236 | 34.2 237 | 55.3 238 | 44.2 239 | 22.0 240 | 19.0 241 | 44.7 242 | 33.2 243 | 55.7 244 | 66 245 | 78 246 | 72 247 | 64.0 248 | 99 249 | 18.0 250 | 17.9 251 | 25.3 252 | 34.2 253 | 55.3 254 | 44.2 255 | 22.0 256 | 19.0 257 | 44.7 258 | 33.2 259 | 55.7 260 | 66 261 | 78 262 | 72 263 | 64.0 264 | 99 265 | 18.0 266 | 17.9 267 | 25.3 268 | 34.2 269 | 55.3 270 | 44.2 271 | 22.0 272 | 19.0 273 | 44.7 274 | 33.2 275 | 55.7 276 | 66 277 | 78 278 | 72 279 | 64.0 280 | 99 281 | 19.0 282 | 44.7 283 | 33.2 284 | 55.7 285 | 66 286 | 78 287 | 72 288 | 64.0 289 | 99 290 | 18.0 291 | 17.9 292 | 25.3 293 | 34.2 294 | 55.3 295 | 44.2 296 | 22.0 297 | 19.0 298 | 44.7 299 | 33.2 300 | 55.7 301 | 66 302 | 78 303 | 72 304 | 64.0 305 | 99 306 | 18.0 307 | 17.9 308 | 25.3 309 | 34.2 310 | 55.3 311 | 44.2 312 | 22.0 313 | 19.0 314 | 44.7 315 | 33.2 316 | 55.7 317 | 66 318 | 78 319 | 72 320 | 64.0 321 | 99 322 | 18.0 323 | 17.9 324 | 25.3 325 | 34.2 326 | 55.3 327 | 44.2 328 | 22.0 329 | 19.0 330 | 44.7 331 | 33.2 332 | 55.7 333 | 66 334 | 78 335 | 72 336 | 64.0 337 | 99 338 | 18.0 339 | 17.9 340 | 25.3 341 | 34.2 342 | 55.3 343 | 44.2 344 | 22.0 345 | 19.0 346 | 44.7 347 | 33.2 348 | 55.7 349 | 66 350 | 78 351 | 72 352 | 64.0 353 | 99 354 | 18.0 355 | 17.9 356 | 25.3 357 | 34.2 358 | 55.3 359 | 44.2 360 | 22.0 361 | 19.0 362 | 44.7 363 | 33.2 364 | 55.7 365 | 66 366 | 78 367 | 72 368 | 64.0 369 | 99 370 | 18.0 371 | 17.9 372 | 25.3 373 | 34.2 374 | 55.3 375 | 44.2 376 | 22.0 377 | 19.0 378 | 44.7 379 | 33.2 380 | 55.7 381 | 66 382 | 78 383 | 72 384 | 64.0 385 | 99 386 | 18.0 387 | 17.9 388 | 25.3 389 | 34.2 390 | 55.3 391 | 44.2 392 | 22.0 393 | 19.0 394 | 44.7 395 | 33.2 396 | 55.7 397 | 66 398 | 78 399 | 72 400 | 64.0 401 | 99 402 | 44.7 403 | 33.2 404 | 55.7 405 | 66 406 | 78 407 | 72 408 | 64.0 409 | 99 410 | 18.0 411 | 17.9 412 | 25.3 413 | 34.2 414 | 55.3 415 | 44.2 416 | 22.0 417 | 19.0 418 | 44.7 419 | 33.2 420 | 55.7 421 | 66 422 | 78 423 | 72 424 | 64.0 425 | 99 426 | 18.0 427 | 17.9 428 | 25.3 429 | 34.2 430 | 55.3 431 | 44.2 432 | 22.0 433 | 19.0 434 | 44.7 435 | 33.2 436 | 55.7 437 | 66 438 | 78 439 | 72 440 | 64.0 441 | 99 442 | 18.0 443 | 17.9 444 | 25.3 445 | 34.2 446 | 55.3 447 | 44.2 448 | 22.0 449 | 19.0 450 | 44.7 451 | 33.2 452 | 55.7 453 | 66 454 | 78 455 | 72 456 | 64.0 457 | 99 458 | 18.0 459 | 17.9 460 | 25.3 461 | 34.2 462 | 55.3 463 | 44.2 464 | 22.0 465 | 19.0 466 | 44.7 467 | 33.2 468 | 55.7 469 | 66 470 | 78 471 | 72 472 | 64.0 473 | 99 474 | 99 475 | 18.0 476 | 17.9 477 | 25.3 478 | 34.2 479 | 55.3 480 | 44.2 481 | 22.0 482 | 19.0 483 | 44.7 484 | 33.2 485 | 55.7 486 | 66 487 | 78 488 | 72 489 | 64.0 490 | 99 491 | 25.3 492 | 34.2 493 | 55.3 494 | 44.2 495 | 22.0 496 | 19.0 497 | 44.7 498 | 33.2 499 | 55.7 500 | 66 501 | 78 502 | 72 503 | 64.0 504 | 99 505 | 18.0 506 | 17.9 507 | 25.3 508 | 34.2 509 | 55.3 510 | 44.2 511 | 22.0 512 | 19.0 513 | 44.7 514 | 33.2 515 | 55.7 516 | 66 517 | 78 518 | 72 519 | 64.0 520 | 99 521 | 18.0 522 | 17.9 523 | 25.3 524 | 34.2 525 | 55.3 526 | 44.2 527 | 22.0 528 | 19.0 529 | 44.7 530 | 33.2 531 | 55.7 532 | 66 533 | 78 534 | 72 535 | 64.0 536 | 99 537 | 18.0 538 | 17.9 539 | 25.3 540 | 34.2 541 | 55.3 542 | 44.2 543 | 22.0 544 | 19.0 545 | 44.7 546 | 33.2 547 | 55.7 548 | 66 549 | 78 550 | 72 551 | 64.0 552 | 99 553 | 19.0 554 | 44.7 555 | 33.2 556 | 55.7 557 | 66 558 | 78 559 | 72 560 | 64.0 561 | 99 562 | 18.0 563 | 17.9 564 | 25.3 565 | 34.2 566 | 55.3 567 | 44.2 568 | 22.0 569 | 19.0 570 | 44.7 571 | 33.2 572 | 55.7 573 | 66 574 | 78 575 | 72 576 | 64.0 577 | 99 578 | 18.0 579 | 17.9 580 | 25.3 581 | 34.2 582 | 55.3 583 | 44.2 584 | 22.0 585 | 19.0 586 | 44.7 587 | 33.2 588 | 55.7 589 | 66 590 | 78 591 | 72 592 | 64.0 593 | 99 594 | 18.0 595 | 17.9 596 | 25.3 597 | 34.2 598 | 55.3 599 | 44.2 600 | 22.0 601 | 19.0 602 | 44.7 603 | 33.2 604 | 55.7 605 | 66 606 | 78 607 | 72 608 | 64.0 609 | 99 610 | 18.0 611 | 17.9 612 | 25.3 613 | 34.2 614 | 55.3 615 | 44.2 616 | 22.0 617 | 19.0 618 | 44.7 619 | 33.2 620 | 55.7 621 | 66 622 | 78 623 | 72 624 | 64.0 625 | 99 626 | 18.0 627 | 17.9 628 | 25.3 629 | 34.2 630 | 55.3 631 | 44.2 632 | 22.0 633 | 19.0 634 | 44.7 635 | 33.2 636 | 55.7 637 | 66 638 | 78 639 | 72 640 | 64.0 641 | 99 642 | 18.0 643 | 17.9 644 | 25.3 645 | 34.2 646 | 55.3 647 | 44.2 648 | 22.0 649 | 19.0 650 | 44.7 651 | 33.2 652 | 55.7 653 | 66 654 | 78 655 | 72 656 | 64.0 657 | 99 658 | 18.0 659 | 17.9 660 | 25.3 661 | 34.2 662 | 55.3 663 | 44.2 664 | 22.0 665 | 19.0 666 | 44.7 667 | 33.2 668 | 55.7 669 | 66 670 | 78 671 | 72 672 | 64.0 673 | 99 674 | --------------------------------------------------------------------------------