├── .gitignore ├── .scalafmt.conf ├── README.adoc ├── build.sbt ├── matryoshka.sc ├── presentation.html ├── project ├── build.properties └── plugins.sbt └── src └── main └── scala └── training └── recursion ├── ex01 └── Ex01_ManualRecursion.scala ├── ex02 └── Ex02_FixedPoint.scala ├── ex03 ├── Ex03_Catamorphism.scala └── Ex03_Traverse.scala ├── ex04 ├── Ex04_Anamorphism.scala └── Ex04_Traverse.scala ├── ex05 ├── Ex05_Paramorphism.scala └── Ex05_Traverse.scala ├── ex06 ├── Ex06_Cofree.scala └── Ex06_Traverse.scala ├── ex07 ├── Ex07_Histomorphism.scala └── Ex07_Traverse.scala └── ex09 └── Ex09_Free.scala /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.ipr 3 | *.iws 4 | .idea/ 5 | target/ 6 | *.log 7 | .DS_Store 8 | application.conf 9 | data/ -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | style = defaultWithAlign 2 | maxColumn = 120 3 | align.openParenCallSite = false 4 | align.openParenDefnSite = false 5 | danglingParentheses = true 6 | 7 | rewrite.rules = [RedundantBraces, RedundantParens, SortImports, PreferCurlyFors] 8 | rewrite.redundantBraces.includeUnitMethods = true 9 | rewrite.redundantBraces.stringInterpolation = true 10 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Recursion schemes 2 | :icons: font 3 | 4 | This repository contains documentation, samples and exercises for the Recursion Schemes workshop. 5 | 6 | TIP: "these things are easier to use than understand" 7 | 8 | Slides from my presentation link:presentation.html[Recursion Schemes by example] 9 | 10 | === Running 11 | Load this project in sbt and just launch `run`, then select the exercise you'd like to execute. 12 | 13 | == Introduction 14 | 15 | [.lead] 16 | Recursive structures are a common pattern and most developers have worked with such data at least a few times. 17 | 18 | === Trees 19 | **Nodes** and **leafs** 20 | 21 | 22 | root-node 23 | / \ 24 | child-node leaf 25 | / \ 26 | leaf leaf 27 | 28 | === List 29 | 30 | Either a `Cons(a, List)` or `Nil` 31 | `Cons(a, Cons(b, Cons(c, Nil))) // List(a, b, c)` 32 | 33 | === Json 34 | 35 | [source, scala] 36 | ---- 37 | sealed trait Json[+F] 38 | object Json { 39 | case object Null extends Json 40 | final case class Bool (value: Boolean) extends Json[Nothing] 41 | final case class Str (value: String) extends Json[Nothing] 42 | final case class Num (value: Double) extends Json[Nothing] 43 | final case class Arr[F](values: List[F]) extends Json[F] 44 | final case class Obj[F](fields: List[(String, F)]) extends Json[F] 45 | } 46 | ---- 47 | 48 | === ASTs 49 | 50 | [source, sql] 51 | SELECT * FROM users u WHERE u.age > 25 AND UPPER(u.name) LIKE "J%" 52 | 53 | 54 | FILTER: 55 | - source: 56 | SELECT 57 | -- selection: * 58 | -- from: users 59 | - criteria: 60 | AND 61 | - GT(u.age, 25) 62 | - LIKE( 63 | UPPER( 64 | u.name) 65 | J%) 66 | 67 | 68 | === What you can do with recursive data 69 | - Print 70 | - Translate into another structure 71 | - Enrich 72 | - Optimize 73 | 74 | 75 | === Manual recursion 76 | 77 | [source, scala] 78 | ---- 79 | sealed trait Expr 80 | 81 | case class IntValue(v: Int) extends Expr 82 | case class DecValue(v: Double) extends Expr 83 | case class Sum(a: Expr, b: Expr) extends Expr 84 | case class Multiply(a: Expr, b: Expr) extends Expr 85 | case class Divide(a: Expr, b: Expr) extends Expr 86 | 87 | def eval(e: Expr): Double = 88 | e match { 89 | case IntValue(v) => v.toDouble 90 | case DecValue(v) => v 91 | case Sum(e1, e2) => eval(e1) + eval(e2) 92 | case Multiply(e1, e2) => eval(e1) * eval(e2) 93 | case Divide(e1, e2) => eval(e1) / eval(e2) 94 | } 95 | ---- 96 | 97 | == Fixed point datatypes 98 | 99 | === Making an ADT polymorphic 100 | 101 | Ideally we'd love to have something more elegant. 102 | We are looking for a tool which takes: 103 | 104 | - A recursive expression of type Expr, 105 | - a function which evaluates `Expr => Double` 106 | For example `case Sum(d1, d2) => d1 + d2` 107 | 108 | Such tool evaluates whole expression to a `Double` 109 | 110 | Types like `Sum(a: Expr, b: Expr)` force us to deal only with Exprs. 111 | Ideally we'd like to have our eval definition to look like: 112 | 113 | [source, scala] 114 | ---- 115 | // does not compile, but it's only an illustration of a direction 116 | def eval(e: Expr): Double = 117 | e match { 118 | case Sum(dbl1: Double, dbl2: Double) => dbl1 + dbl2 // etc 119 | } 120 | ---- 121 | 122 | Let's make our expression **polymorphic**. 123 | 124 | [source, scala] 125 | ---- 126 | sealed trait Expr[A] 127 | 128 | case class IntValue[A](v: Int) extends Expr[A] 129 | case class DecValue[A](v: Double) extends Expr[A] 130 | case class Sum[A](a: A, b: A) extends Expr[A] 131 | case class Multiply[A](a: A, b: A) extends Expr[A] 132 | case class Divide[A](a: A, b: A) extends Expr[A] 133 | ---- 134 | 135 | That's much better, because this allows us express our evaluations as: 136 | 137 | [source, scala] 138 | ---- 139 | def evalToDouble(exp: Expr[Double]): Double = exp match { 140 | case IntValue(v) => v.toDouble 141 | case DecValue(v) => v 142 | case Sum(d1, d2) => d1 + d2 143 | case Multiply(d1, d2) => d1 * d2 144 | case Divide(d1, d2) => d1 / d2 145 | } 146 | ---- 147 | 148 | Such evaluation is what we aim for, because it doesn't look like 149 | recursion. It looks more like a set of rules, which we can **apply** to 150 | a recursive structure with some blackbox tool which will recursively 151 | build the result. 152 | 153 | But let's stop here for a while, because polymorphic expressions 154 | come with a cost... First, consider a trivial expression: 155 | [source, scala] 156 | val intVal = IntValue[Unit](10) // Expr[Unit] 157 | 158 | What about more complex expressions? 159 | 160 | [source,scala] 161 | ---- 162 | val sumExp: Expr[Expr[Unit]] = 163 | Sum( 164 | IntValue[Unit](10), // Expr[Unit] 165 | IntValue[Unit](5) 166 | ) 167 | ---- 168 | 169 | === Fixing nested Exprs 170 | 171 | how to deal with types like `Expr[Expr[Expr[A]]]`? 172 | Let's wrap in: 173 | 174 | [source, scala] 175 | ---- 176 | case class Fix[F[_]](unFix: F[Fix[F]]) 177 | 178 | val fixedIntExpr: Fix[Expr] = Fix(IntValue[Fix[Expr]](10)) 179 | ---- 180 | 181 | The `Fix` type allows us to represent any `Expr[Expr[Expr....[A]]]` as `Fix[Expr]` 182 | 183 | Wait, why did we need this`Fix` thing? 184 | 185 | === A step back 186 | 187 | We wanted to use evaluation definition which doesn't look like recursion. 188 | 189 | We are looking for a tool which takes: 190 | 191 | - A recursive expression of type Expr, 192 | - a function which evaluates a single simple `Expr => Double` 193 | For example `case Sum(d1, d2) => d1 + d2` 194 | 195 | To be able to express such rules, we needed to go from `Expr` to `Expr[A]`. 196 | To avoid issues with nested types, we introduced `Fix[Expr]` 197 | 198 | === Putting it all together 199 | 200 | Once we have: 201 | 202 | - A polymorphic recursive structure based on `Expr[A]` 203 | - An evaluation recipe expressed as a set of rules for each sub-type (`Expr[B] => B`) 204 | - A `Fix[F[_]]` wrapper 205 | 206 | We can now use a tool to put this all together. Such tool is called... 207 | 208 | == Catamorphism 209 | 210 | === Scheme 211 | 212 | A generic **foldRight** for data stuctures. In case of recursive data, 213 | this means **folding bottom-up**: 214 | 215 | [source, scala] 216 | val division = 217 | Divide(DecValue(5.2), Sum(IntValue(10), IntValue(5))) 218 | 219 | ``` 220 | Divide Divide 221 | / \ / \ 222 | DecValue(5.2) Sum --> DecValue(5.2) Sum 223 | / \ / \ 224 | IntValue(10) IntValue(5) 10.0 5.0 225 | ``` 226 | 227 | ``` 228 | Divide Divide 229 | / \ / \ 230 | DecValue(5.2) Sum --> 5.2 15.0 231 | / \ 232 | 10.0 5.0 233 | ``` 234 | 235 | ``` 236 | Divide --> 5.2 / 15.0 237 | / \ 238 | 5.2 15.0 239 | ``` 240 | 241 | Going **bottom-up**, we use our set of rules on leafs, then we build 242 | higher nodes **basing** on lower nodes. Catamorphism is a **generic** tool, 243 | so you don't have to implement it! 244 | 245 | === Matryoshka and cata 246 | 247 | The Matryoshka library does catamorphism for you: 248 | 249 | [source, scala] 250 | ---- 251 | val recursiveExpr: Fix[Expr] = ??? // your tree 252 | 253 | def evalToDouble(expr: Expr[Double]): Double 254 | 255 | // the magic call 256 | recursiveExpression.cata(evalToDouble) // returns Double 257 | ---- 258 | 259 | The `.cata()` call runs the whole folding process and constructs 260 | the final `Double` value for you, provided just a set of rules for 261 | indiviual node types. 262 | 263 | === Expression functor 264 | 265 | Matryoshka's `.cata()` is a blackbox, but it has one more requirement. 266 | It's mechanism assumes that a `Functor` instance is available for your datatype. 267 | 268 | This means that you must provide a recipe for how to **map** `Expr[A]` to `Expr[B]` 269 | having a function `f: A => B`. 270 | For example to transform `Sum[A](a1: A, a2: A)` into `Sum[B](b1: B, b2: B)` you need 271 | to do `Sum(f(a1), f(a2))`. Such recipe has to be provided for all 272 | possible cases of `Expr`. 273 | 274 | [source, scala] 275 | ---- 276 | import scalaz.Functor 277 | 278 | implicit val exprFunctor: Functor[Expr] = new Functor[Expr] { 279 | override def map[A, B](expr: Expr[A])(f: A => B): Expr[B] = expr match { 280 | case IntVal(v) => IntVal[B](v) 281 | case Sum(a1, a2) => Sum(f(a1), f(a2)) 282 | case ... // etc. 283 | } 284 | } 285 | ---- 286 | 287 | This is finally all what we need! Here's a summary of our ingredients: 288 | 289 | 1. A recursive structure `Expr[A]` 290 | 2. A Functor for this type 291 | 3. A set or evaulation rules for **individual cases** 292 | 4. `Fix[[_]]` 293 | 5. catamorphism 294 | 295 | 4 & 5 are provided by Matryoshka. 296 | 297 | === Algebra 298 | 299 | Our evaluation function (point 3.) is called an **Algebra**. From Matryoshka: 300 | 301 | [source, scala] 302 | ---- 303 | type Algebra[F[_], A] = F[A] => A 304 | 305 | def evalToDouble(expr: Expr[Double]): Double 306 | 307 | val evalToDouble: Algebra[Expr, Double] 308 | ---- 309 | 310 | === Some syntax sugar to work with Fix 311 | 312 | Remember this? 313 | 314 | [source, scala] 315 | Fix(Sum(Fix(IntValue[Fix[Expr]](10)), Fix(IntValue[Fix[Expr]](5)))) 316 | 317 | There`s some syntax sugar to help: 318 | [source, scala] 319 | Sum(IntValue[Fix[Expr]](10).embed, IntValue[Fix[Expr]](5).embed).embed 320 | 321 | Handy especially for larger expressions (see exercise 03). To unpack from `Fix`, you can 322 | use `unFix`: 323 | 324 | [source, scala] 325 | ---- 326 | val fixedSum: Fix[Expr] = 327 | Sum( 328 | IntValue[Fix[Expr]](10).embed, 329 | IntValue[Fix[Expr]](5).embed 330 | ).embed 331 | 332 | fixedSum.unFix match { 333 | case Sum(...) => 334 | } 335 | ---- 336 | 337 | `unFix` is fine, but instead use `.project` which does the same, but is a more general 338 | function which work on other recursive wrappers. Sorry for the spoiler, but `Fix` is not 339 | the only one around, and you don't want to get tied directly to it! 340 | 341 | === Transforming recursive expressions 342 | 343 | Let's say we have an `Expr` and we want to optimize it to express `Multiply(x, x)` as `Square(x)`. 344 | We'd like to have a tool which walks our tree and **maps** a given `Expr` to another `Expr` 345 | 346 | ``` 347 | Divide Divide 348 | / \ / \ 349 | DecValue(5.2) Multiply --> DecValue(5.2) Square(10) 350 | / \ 351 | IntValue(10) IntValue(10) 352 | ``` 353 | 354 | We are looking for a function like: 355 | 356 | [source, scala] 357 | ---- 358 | def mapNode(t: Fix[Expr])(f: Fix[Expr] => Fix[Expr]) 359 | 360 | def optimize(expr: Fix[Expr]): Fix[Expr] = expr.project match { 361 | case Multiply(a, b) if (a == b) => Square(a) 362 | case other => other 363 | } 364 | 365 | val optimizedExpr = mapNode(exprTree)(optimize) 366 | ---- 367 | 368 | Matryoshka offers such a tool, and it's called `transCataT`: 369 | 370 | [source, scala] 371 | val optimizedExpr: Fix[Expr] = exprTee.transCataT(optimize) 372 | 373 | ==== cataM 374 | 375 | Sometimes our evaluation produces a wrapped value. Suppose we want to evaluate the 376 | expression to a Double, but handle division by zero by wrapping the result in an `Either`. 377 | Here's our new evaluation: 378 | 379 | [source, scala] 380 | ---- 381 | def evalToDouble(exp: Expr[Double]): \/[String, Double] = exp match { 382 | case IntValue(v) => v.toDouble.right 383 | case _ => ??? // etc 384 | } 385 | ---- 386 | 387 | We can't just use `cata`, because `cata` works with `Algebra`. 388 | 389 | [source, scala] 390 | ---- 391 | type Algebra[F[_], A] = F[A] => A 392 | 393 | // F[A] => M[A], where M[A] means Either[String, A] 394 | def evalToDouble(exp: Expr[Double]): Either[String, Double] 395 | ---- 396 | 397 | `Algebra` is a function `type Algebra[F[_], A] = F[A] => A`, while our new evaluation is 398 | of type `F[A] => M[A]`. If our evaluation has such signature, we can use **cataM**: 399 | 400 | [source, scala] 401 | ---- 402 | val correctExpr: Fix[Expr] = 403 | Sum( 404 | DecValue[Fix[Expr]](5.2).embed, 405 | Divide( 406 | DecValue[Fix[Expr]](3.0).embed, 407 | DecValue[Fix[Expr]](3.0).embed 408 | ).embed 409 | ).embed 410 | 411 | val incorrectExpr: Fix[Expr] = 412 | Sum( 413 | DecValue[Fix[Expr]](5.2).embed, 414 | Divide( 415 | DecValue[Fix[Expr]](3.0).embed, 416 | DecValue[Fix[Expr]](0.0).embed // !!!!!!!! 417 | ).embed 418 | ).embed 419 | 420 | correctExpr.cataM(evalToDouble) // Right(6.2) 421 | incorrectExpr.cataM(evalToDouble) // Left("Division by zero!") 422 | ---- 423 | 424 | However, there's one more requirement. Our `Functor[Expr]` is not enough, Matryoshka needs 425 | a `Traverse[Expr]`. 426 | 427 | TIP: Instead of writing a `Functor` for your recursive data type, write a `Traverse`. It is 428 | also a `Functor`, it's pretty much the same amount of work, and it may become useful in case 429 | you need `cataM`. 430 | 431 | === Anamorphism 432 | 433 | As we learned, cata is a bottom-up folding of a structure. Anamorphism works in the opposite direction 434 | and allows to unfold a structure. For this we need the dual of `Algebra` - `Coalgebra`: 435 | [source, scala] 436 | type Coalgebra[F[_], A] = A => F[A] 437 | 438 | Such morphism is called an unfold, because it takes an object and recursively builds up a structure 439 | basing on it. 440 | 441 | [source, scala] 442 | ---- 443 | // Int => Expr[Int] 444 | val toBinary: Coalgebra[Expr, Int] = (n: Int) => 445 | n match { 446 | case 0 => IntValue(0) 447 | case 1 => IntValue(1) 448 | case 2 => IntValue(2) 449 | case _ if n % 2 == 0 => Multiply(2, n / 2) 450 | case _ => Sum(1, n - 1) 451 | } 452 | 453 | val toText: Algebra[Expr, String] = { 454 | case IntValue(v) => v.toString 455 | case Sum(a, b) => s"($a + $b)" 456 | case Multiply(a, b) => s"($a * $b)" 457 | } 458 | 459 | // unfold with anamorphism 460 | val expr = 31.ana.apply[Fix[Expr]](toBinary) 461 | // and now fold with catamorphism 462 | val binAsStr = expr.cata(toText) // (1 + (2 * (1 + (2 * (1 + (2 * (1 + 2))))))) 463 | ---- 464 | 465 | A composition of ana+cata is called **hylomorphism** 466 | 467 | [source, scala] 468 | val binAsStr = 31.hylo(toText, toBinary) 469 | 470 | == Paramorphism 471 | 472 | Also a fold, very similar to catamorphism. However, it adds one more extra feature - 473 | with paramorphism, you not only build current state basing on evaluation of previous states, 474 | but you also have access to these states. Confusing? Here's an example: 475 | Let's say we want to use a special algorithm for a specific case: 476 | 477 | We want to print `(3 + -5)` as `(5 - 3)` for better readability. 478 | 479 | With cata, we don't have enough information except Strings: 480 | 481 | [source, scala] 482 | case Sum(left: String, right: String) => ??? // what was left and right before their evaluation? 483 | 484 | Parsing Strings here doesn't seem like a good idea. With para, we can use a richer kind of algebra: 485 | 486 | [source, scala] 487 | ---- 488 | case Sum((leftSrc, leftStr), (rightSrc, rightStr)) => 489 | leftSrc.project match { 490 | case IntValue(a) => 491 | rightSrc.project match { 492 | case IntValue(b) if a > 0 && b < 0 => s"($a - ${-b})" 493 | case IntValue(b) if b > 0 && a < 0 => s"($b - ${-a})" 494 | case _ => s"$leftStr + $rightStr" 495 | } 496 | case _ => s"$leftStr + $rightStr" 497 | } 498 | ---- 499 | 500 | This is a different kind of Algebra: `Expr[(Fix[Expr], String)] => String`, so it's 501 | `F[(T, A)] => A`, where `T` means `Fix[Expr]` for this particular case. 502 | To be more precise, it's 503 | 504 | [source, scala] 505 | type GAlgebra[W[_], F[_], A] = F[W[A]] => A 506 | 507 | Where in our case `F[W[A]]` is `Expr[Tuple2[T, A]]`. 508 | Paramorphism is useful when we need to know more about node's childrens' structure in order to fully 509 | evaulate that node. 510 | 511 | Paramorphisms are generalized folds with access to the input argument corresponding 512 | to the most recent state of the computation. 513 | 514 | However, para is limited, because we only know the source **structure**. If you need more, 515 | see *histomorphism*. But let's take a break from morphisms and check out some other interesting 516 | concepts. 517 | 518 | === Cofree 519 | 520 | Cofree is a very broad concept. It's actually a "comonad" with quite a few interesting attributes and applications. 521 | For the sake of this course, let's consider `Cofree[S, A]` to be a **pair** of: 522 | 523 | 1. a value of type `A` 524 | 2. A recursive expression `S` which can consist of ... deeper elements of type `Cofree`. 525 | 526 | `Cofree` is often used to construct labelled expression. Each node in an expression gets an extra label (tag). 527 | For a simple DSL: 528 | 529 | [source, scala] 530 | ---- 531 | sealed trait Expr[A] 532 | 533 | case class IntValue[A](v: Int) extends Expr[A] 534 | case class DecValue[A](v: Double) extends Expr[A] 535 | case class Sum[A](a: A, b: A) extends Expr[A] 536 | case class Square[A](a: A) extends Expr[A] 537 | ---- 538 | 539 | we can label any `Expr` with an `ExprType`: 540 | 541 | [source, scala] 542 | ---- 543 | sealed trait ExprType 544 | case object IntExpr extends ExprType 545 | case object DecExpr extends ExprType 546 | ---- 547 | 548 | Our rules can be simple: a sum of integers is an integer. A sum of (int + dec) is a decimal. A square of a type 549 | has the same type. A **tagged** expression is of type `Cofree[Expr, ExprType]`. Such object cosists of: 550 | 551 | 1. A `head: ExprType` which is the tag (label) placed on the node. 552 | 2. A `tail: Expr[Cofree[Expr, ExprType]]` which is the expression itself (`Expr[A]`) where `A` is a deeper level of `Cofree`. 553 | 554 | We can recursively apply such labelling with catamorphism, using an `Algebra[Expr, Cofree[Expr, ExprType]]` 555 | 556 | [source, scala] 557 | ---- 558 | val inferType: Algebra[Expr, Cofree[Expr, ExprType]] = { 559 | case IntValue(v) => Cofree.apply(IntExpr, IntValue(v)) 560 | case DecValue(v) => Cofree.apply(DecExpr, DecValue(v)) 561 | case Sum(a, b) => ??? // a: Cofree[Expr, ExprType]] 562 | } 563 | ---- 564 | 565 | `Cofree` is much more than a tuple with extras. It's a *fixed-point operator*. This means that it has the same power 566 | as `Fix`. Having an expression wrapped with `Cofree`, we can apply morphisms directly to it! 567 | 568 | [source, scala] 569 | val typedExpr: Cofree[Expr, ExprType] = expr.cata(inferType) 570 | 571 | // No need for Fix[] 572 | val toTypedStr: Algebra[EnvT[ExprType, Expr, ?], String] = { 573 | case EnvT((exprType, Sum(a, b))) => s"($a + $b): $exprType" // (3 + 5.5): DecValue 574 | case _ => "..." 575 | } 576 | 577 | This reveals one more mystery - there are more fixed point operators than just `Fix` and `Cofree`. 578 | 579 | == Histomorphism 580 | 581 | Histomporphism is a fold which also provides something akin to a "pair". While in paramorphism for each node we 582 | could access pairs of (child_evaluated + child_sourceExpression), in histomorphism we work with 583 | (child_evaluated + child_tree). This child_tree can be traversed further down. To represent this whole pair, we 584 | use `Cofree`. 585 | 586 | [source, scala] 587 | ---- 588 | val smartPrint: GAlgebra[Cofree[Expr, ?], Expr, String] = { 589 | case Square(Cofree(a: String, history: Expr[Cofree[Expr, String]])) => 590 | history match { 591 | case Sum(Cofree(a, aHistory), Cofree(b, bHistory)) => ... 592 | case _ => ... 593 | } 594 | case _ => ... 595 | } 596 | ---- 597 | 598 | We can `unpack` each node to desired depth, because history elements carry everything that has 599 | been computed up to this point. 600 | 601 | == Fixed point operators 602 | 603 | So far we considered `Fix[]` to be a kind of type-level trick to deal with recursive expressions. For most applications 604 | such approach should be enough. Here's a bit more format explanation. 605 | 606 | In math, a fixed point of a function `f` is a value `a` such that 607 | 608 | f(a) == a 609 | 610 | We can now say that `fix` is a function such that 611 | 612 | fix(f) == f(fix(f)) 613 | 614 | Does this look familiar? For types, we had: 615 | 616 | [source, scala] 617 | ---- 618 | val fix = Fix[Expr] 619 | val expr: Expr[Fix[Expr]] = fix.unfix 620 | ---- 621 | 622 | ==== Mu 623 | 624 | Mu is a more restricted version of `Fix`. It can be used to represent inductive finite data. 625 | We can replace our previous usages of `Fix` with `Mu`. 626 | 627 | ==== Nu 628 | 629 | Nu can be used to represent infinite coinductive data (like streams). 630 | 631 | ==== List 632 | [source, scala] 633 | ---- 634 | sealed abstract class ListF[A, B] 635 | final case class NilF[A, B]() extends ListF[A, B] 636 | final case class ConsF[A, B](head: A, tail: B) extends ListF[A, B] 637 | 638 | type List[A] = Mu[ListF[A, ?]] 639 | ---- 640 | 641 | // TODO exercise 642 | 643 | ==== Cofree 644 | 645 | Cofree is a vast subject in itself. It's a "comonad" with many interesting properties, but 646 | for the sake of this course we are interested in its recursive character. In a simplicit view, `Cofree` is: 647 | 648 | - some value of type `A` 649 | 650 | plus 651 | 652 | - an expression with nested `Cofree`(s) 653 | 654 | In other words, `Cofree` can be viewed as a recursive structure similar to `Fix[Expr]` but with additional 655 | labels (tags) placed on every node of our tree. 656 | 657 | One example may be adding a type system to our DSL. This allows putting a "type" tag on each node of an expression 658 | tree. For example, let's decompose a `Sum(IntVal(3), Sum(IntVal(5), DecVal(5.5)))`: 659 | 660 | If we encode a rule that `(Int + Dec)` has type `Dec`, then our expression becomes: 661 | 662 | ``` 663 | Sum: Dec 664 | / \ 665 | / Sum: Dec 666 | / / \ 667 | 3: Int 5: Int 5.5: Dec 668 | ``` 669 | 670 | With recursion schemes, we can construct a `Cofree` representing our expression with labels using catamorphism. 671 | Then we can run further morphisms on a Cofree without referring to `Fix` or any other wrapper, since `Cofree` is 672 | such a wrapper in itself. See exercises for examples. 673 | 674 | ==== Recursive / Corecursive 675 | 676 | Finally we can say that all mentioned types: `Fix`, `Mu`, `Nu`, `List`, `Cofree` and others share common properties. 677 | These properties are grouped under the `Recursive[T]` and `Corecursive[T]` typeclasses. These types are generalizations 678 | which allow us to write even more abstract code and stay independent of particular wrapper type if it's possible. 679 | That's why we learned to use `.project` and `.embed` - functions from these type classes, instead of `unfix` which was 680 | specific to `Fix`. 681 | 682 | ==== Free 683 | 684 | Another interesting `Birecursive[T]` (which means both `Recursive` and `Corecursive`) is `Free`. Yes, *that* Free. 685 | A free monad also can be run through morphisms! After all, free monads are often used to work with DSLs describing 686 | some programs. Such program description is similar to our `Expr[A]`. 687 | 688 | If you're familiar with free monads, you probably recognize `foldMap`. It's nothing more but a 689 | catamorphism! 690 | 691 | == Sources and resources 692 | 693 | - Paweł Szulc: Going bananas with recursion schemes (https://www.youtube.com/watch?v=IlvJnkWH6CA) 694 | - David Barri: Practical awesome recursion (https://japgolly.blogspot.com/2017/11/practical-awesome-recursion-ch-01.html) 695 | - Greg Pfeil: Recursion schemes (https://github.com/sellout/recursion-scheme-talk/blob/master/matryoshka.org) 696 | - Quasar (https://github.com/quasar-analytics/quasar) 697 | - Blog: Maciek Makowski (https://notepad.mmakowski.com/Recursion%20Schemes?revision=1a0cbb1a636e157a26f6a7175e91328cf26a2573#zygomorphism) -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.4" 2 | 3 | name := "recursion-training" 4 | organization := "com.softwaremill" 5 | version := "1.0" 6 | 7 | scalafmtVersion in ThisBuild := "1.4.0" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.slamdata" %% "matryoshka-core" % "0.18.3", 11 | "com.lihaoyi" % "ammonite" % "1.1.0" % "test" cross CrossVersion.full) 12 | 13 | addCompilerPlugin("io.tryp" % "splain" % "0.2.10" cross CrossVersion.patch) 14 | 15 | addCompilerPlugin("com.softwaremill.clippy" %% "plugin" % "0.5.3" classifier "bundle") 16 | 17 | addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.6") 18 | 19 | scalacOptions ++= Seq( 20 | "-deprecation", // Emit warning and location for usages of deprecated APIs. 21 | "-encoding", "UTF-8", // Specify character encoding used by source files. 22 | "-explaintypes" , // Explain type errors in more detail. 23 | "-language:existentials", // Existential types (besides wildcard types) can be written and inferred 24 | "-language:higherKinds", // Allow higher-kinded types 25 | "-language:experimental.macros", // Allow macro definition (besides implementation and application) 26 | "-language:implicitConversions", // Allow definition of implicit functions called views 27 | "-language:postfixOps", // Allow postfix operators 28 | "-Xfuture", // Turn on future language features. 29 | "-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver. 30 | "-Ypartial-unification", 31 | "-P:splain:all:true", 32 | "-P:clippy:colors=true") 33 | 34 | 35 | scalacOptions ++= List("-P:splain:all:true", "-Ypartial-unification", "-P:clippy:colors=true") 36 | 37 | // Ammonite 38 | sourceGenerators in Test += Def.task { 39 | val file = (sourceManaged in Test).value / "amm.scala" 40 | IO.write(file, """object amm extends App { ammonite.Main.main(args) }""") 41 | Seq(file) 42 | }.taskValue -------------------------------------------------------------------------------- /matryoshka.sc: -------------------------------------------------------------------------------- 1 | import scalaz._ 2 | import Scalaz._ 3 | import matryoshka._ 4 | import matryoshka.implicits._ 5 | import matryoshka.patterns._ 6 | import matryoshka.data._ -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.1.2 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.1") 2 | 3 | addSbtPlugin("com.lucidchart" % "sbt-scalafmt-coursier" % "1.15") 4 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex01/Ex01_ManualRecursion.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex01 2 | 3 | // -------------------- the DSL -------------------- 4 | sealed trait Expr 5 | 6 | case class IntValue(v: Int) extends Expr 7 | case class DecValue(v: Double) extends Expr 8 | case class Sum(a: Expr, b: Expr) extends Expr 9 | case class Multiply(a: Expr, b: Expr) extends Expr 10 | case class Square(a: Expr) extends Expr 11 | case class Divide(a: Expr, b: Expr) extends Expr 12 | // ------------------------------------------------- 13 | 14 | object Ex01_ManualRecursion extends App { 15 | 16 | // it's not even tailrec :( 17 | def eval(e: Expr): Double = 18 | e match { 19 | case IntValue(v) => v.toDouble 20 | case DecValue(v) => v 21 | case Sum(e1, e2) => eval(e1) + eval(e2) // would love to have case Sum(e1: Double, e2: Double) => e1 + e2 22 | case Multiply(e1, e2) => eval(e1) * eval(e2) 23 | case Square(e) => eval(e) * eval(e) 24 | case Divide(e1, e2) => eval(e1) / eval(e2) 25 | } 26 | 27 | def prettyPrint(e: Expr): String = ??? // TODO 28 | 29 | def optimize(e: Expr): Expr = ??? // TODO Multiply(x, x) -> Square(x) 30 | 31 | val expr1 = Sum(IntValue(3), Multiply(IntValue(5), DecValue(-2))) 32 | 33 | println(s"Evaluated $expr1: ${eval(expr1)}") 34 | println(s"Pretty-printed $expr1: ${prettyPrint(expr1)}") 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex02/Ex02_FixedPoint.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex02 2 | 3 | // -------------------- the DSL -------------------- 4 | sealed trait Expr[A] 5 | 6 | case class IntValue[A](v: Int) extends Expr[A] 7 | case class DecValue[A](v: Double) extends Expr[A] 8 | case class Sum[A](a: A, b: A) extends Expr[A] 9 | case class Multiply[A](a: A, b: A) extends Expr[A] 10 | case class Divide[A](a: A, b: A) extends Expr[A] 11 | // ------------------------------------------------- 12 | 13 | case class Fix[F[_]](unFix: F[Fix[F]]) 14 | 15 | object Ex02_FixedPoint extends App { 16 | 17 | // a set of rules 18 | def evalToDouble(exp: Expr[Double]): Double = exp match { 19 | case IntValue(v) => v.toDouble 20 | case DecValue(v) => v 21 | case Sum(d1, d2) => d1 + d2 22 | case Multiply(d1, d2) => d1 * d2 23 | case Divide(d1, d2) => d1 / d2 24 | } 25 | 26 | val intExpr = IntValue[Unit](10) // Expr[Unit] 27 | 28 | val sumExp: Expr[Expr[Unit]] = 29 | Sum( 30 | IntValue[Unit](10), // Expr[Unit] 31 | IntValue[Unit](5) 32 | ) 33 | 34 | val division = // TODO type? 35 | Divide(DecValue(5.2), Sum(IntValue[Unit](10), IntValue[Unit](5))) 36 | 37 | val fixedIntExpr: Fix[Expr] = Fix(IntValue[Fix[Expr]](10)) 38 | 39 | val fixedSum: Fix[Expr] = Fix( 40 | Sum( 41 | Fix(IntValue[Fix[Expr]](10)), 42 | Fix(DecValue[Fix[Expr]](5.5)) 43 | ) 44 | ) 45 | 46 | val fixedDivision: Fix[Expr] = ??? // TODO fix the division expression to match type 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex03/Ex03_Catamorphism.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex03 2 | 3 | import scalaz._ 4 | import Scalaz._ 5 | 6 | // -------------------- the DSL -------------------- 7 | sealed trait Expr[A] 8 | 9 | case class IntValue[A](v: Int) extends Expr[A] 10 | case class DecValue[A](v: Double) extends Expr[A] 11 | case class Sum[A](a: A, b: A) extends Expr[A] 12 | case class Multiply[A](a: A, b: A) extends Expr[A] 13 | case class Divide[A](a: A, b: A) extends Expr[A] 14 | case class Square[A](a: A) extends Expr[A] 15 | // ------------------------------------------------- 16 | 17 | object Ex03_Catamorphism extends App with Ex03_Traverse { 18 | 19 | import matryoshka.data._ 20 | import matryoshka._ 21 | import matryoshka.implicits._ 22 | 23 | // a set of rules 24 | def evalToDouble(expr: Expr[Double]): Double = expr match { 25 | case IntValue(v) => v.toDouble 26 | case DecValue(v) => v 27 | case Sum(d1, d2) => d1 + d2 28 | case Multiply(d1, d2) => d1 * d2 29 | case Divide(d1, d2) => d1 / d2 30 | case Square(d) => d * d 31 | } 32 | 33 | val sumExpr: Fix[Expr] = Fix( 34 | Sum( 35 | Fix(IntValue[Fix[Expr]](10)), 36 | Fix(DecValue[Fix[Expr]](5.5)) 37 | ) 38 | ) 39 | 40 | // comment this out and recompile to see an implicit resolution error 41 | implicit val ExprFunctor: Functor[Expr] = ??? // TODO 42 | 43 | println(s"Expression: $sumExpr\nExpr evaluated to double: ${sumExpr.cata(evalToDouble)}") 44 | 45 | // fix sugar 46 | val division = 47 | Divide(DecValue(5.2), Sum(IntValue[Unit](10), IntValue[Unit](5))) 48 | 49 | val fixedSum: Fix[Expr] = 50 | Sum( 51 | IntValue[Fix[Expr]](10).embed, // extension method 52 | IntValue[Fix[Expr]](5).embed 53 | ).embed 54 | 55 | val fixedDivision: Fix[Expr] = ??? // TODO use .embed 56 | 57 | // optimization 58 | def optimizeSqr(expr: Fix[Expr]): Fix[Expr] = ??? // TODO (use .project and .embed) 59 | 60 | // how to apply this function? 61 | // transCataT 62 | val initialExpr: Fix[Expr] = 63 | Sum( 64 | DecValue[Fix[Expr]](5.2).embed, 65 | Multiply( 66 | DecValue[Fix[Expr]](3.0).embed, 67 | DecValue[Fix[Expr]](3.0).embed 68 | ).embed 69 | ).embed 70 | 71 | val optimizedExpr = initialExpr.transCataT(optimizeSqr) 72 | println(optimizedExpr) 73 | 74 | // cataM 75 | 76 | // AlgebraM 77 | def evalToDoubleOrErr(exp: Expr[Double]): \/[String, Double] = exp match { 78 | case _ => ??? // TODO 79 | } 80 | 81 | val correctExpr: Fix[Expr] = 82 | Sum( 83 | DecValue[Fix[Expr]](5.2).embed, 84 | Divide( 85 | DecValue[Fix[Expr]](3.0).embed, 86 | DecValue[Fix[Expr]](3.0).embed 87 | ).embed 88 | ).embed 89 | 90 | val incorrectExpr: Fix[Expr] = 91 | Sum( 92 | DecValue[Fix[Expr]](5.2).embed, 93 | Divide( 94 | DecValue[Fix[Expr]](3.0).embed, 95 | DecValue[Fix[Expr]](0.0).embed // !!!!!!!! 96 | ).embed 97 | ).embed 98 | 99 | implicit val traverse = traverseExpr 100 | 101 | correctExpr.cataM(evalToDoubleOrErr) // Right(6.2) 102 | incorrectExpr.cataM(evalToDoubleOrErr) // Left("Division by zero!") 103 | } 104 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex03/Ex03_Traverse.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex03 2 | 3 | import scalaz._ 4 | import Scalaz._ 5 | 6 | trait Ex03_Traverse { 7 | // it's also a Functor[Expr] 8 | val traverseExpr: Traverse[Expr] = new Traverse[Expr] { 9 | 10 | override def traverseImpl[G[_], A, B](fa: Expr[A])(f: A => G[B])(implicit G: Applicative[G]): G[Expr[B]] = 11 | fa match { 12 | case IntValue(v) => G.point(IntValue(v)) 13 | case DecValue(v) => G.point(DecValue(v)) 14 | // f(a1): Either[String, Double], f(a2): Either[String, Double] 15 | case Sum(a1, a2) => G.apply2(f(a1), f(a2))(Sum.apply) // or nicer: (f(a1) ⊛ f(a2))(Sum.apply) 16 | case Multiply(a1, a2) => (f(a1) ⊛ f(a2))(Multiply.apply) 17 | case Divide(a1, a2) => (f(a1) ⊛ f(a2))(Divide.apply) 18 | case Square(a) => f(a) ∘ Square.apply 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex04/Ex04_Anamorphism.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex04 2 | 3 | import matryoshka._ 4 | import matryoshka.implicits._ 5 | import matryoshka.data._ 6 | 7 | // -------------------- the DSL -------------------- 8 | sealed trait Expr[A] 9 | 10 | case class IntValue[A](v: Int) extends Expr[A] 11 | case class Sum[A](a: A, b: A) extends Expr[A] 12 | case class Multiply[A](a: A, b: A) extends Expr[A] 13 | // ------------------------------------------------- 14 | 15 | object Ex04_Anamorphism extends App with Ex04_Traverse { 16 | 17 | // Int => Expr[Int] consisting of only 1s and 2s 18 | val toBinary: Coalgebra[Expr, Int] = (n: Int) => 19 | n match { 20 | case _ => ??? // TODO 21 | } 22 | 23 | val toText: Algebra[Expr, String] = { 24 | case IntValue(v) => v.toString 25 | case Sum(a, b) => s"($a + $b)" 26 | case Multiply(a, b) => s"($a * $b)" 27 | } 28 | 29 | val expr = 31.ana.apply[Fix[Expr]](toBinary) 30 | println(expr.cata(toText)) 31 | println(31.hylo(toText, toBinary)) 32 | } 33 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex04/Ex04_Traverse.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex04 2 | 3 | import scalaz.Scalaz._ 4 | import scalaz._ 5 | 6 | trait Ex04_Traverse { 7 | // it's also a Functor[Expr] 8 | implicit val traverseExpr: Traverse[Expr] = new Traverse[Expr] { 9 | 10 | override def traverseImpl[G[_], A, B](fa: Expr[A])(f: A => G[B])(implicit G: Applicative[G]): G[Expr[B]] = 11 | fa match { 12 | case IntValue(v) => G.point(IntValue(v)) 13 | case Sum(a1, a2) => (f(a1) ⊛ f(a2))(Sum.apply) 14 | case Multiply(a1, a2) => (f(a1) ⊛ f(a2))(Multiply.apply) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex05/Ex05_Paramorphism.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex05 2 | 3 | import matryoshka.data.Fix 4 | import matryoshka._ 5 | import matryoshka.implicits._ 6 | import scalaz._ 7 | import Scalaz._ 8 | 9 | // -------------------- the DSL -------------------- 10 | sealed trait Expr[A] 11 | 12 | case class IntValue[A](v: Int) extends Expr[A] 13 | case class Sum[A](a: A, b: A) extends Expr[A] 14 | case class Multiply[A](a: A, b: A) extends Expr[A] 15 | case class Square[A](a: A) extends Expr[A] 16 | // ------------------------------------------------- 17 | 18 | object Ex05_Paramorphism extends App with Ex05_Traverse { 19 | 20 | // handy utility functions if you want to build expressions by hand 21 | def int(i: Int): Fix[Expr] = IntValue[Fix[Expr]](i).embed 22 | def sum(a: Int, b: Int): Fix[Expr] = Sum(int(a), int(b)).embed 23 | def multiply(a: Fix[Expr], b: Fix[Expr]): Fix[Expr] = Multiply(a, b).embed 24 | def square(e: Fix[Expr]): Fix[Expr] = Square(e).embed 25 | 26 | // here: Expr[(Fix[Expr], String)] => String 27 | def algebra(srcAndExpr: (Expr[(Fix[Expr], String)])): String = srcAndExpr match { 28 | case IntValue(v) => v.toString 29 | case Sum((leftExpr, leftStr), (rightExpr, rightStr)) => 30 | leftExpr.project match { 31 | case IntValue(a) => 32 | rightExpr.project match { 33 | case IntValue(b) if a > 0 && b < 0 => s"($a - ${-b})" 34 | case IntValue(b) if b > 0 && a < 0 => s"($b - ${-a})" 35 | case _ => s"($leftStr + $rightStr)" 36 | } 37 | case _ => s"$leftStr + $rightStr" 38 | } 39 | case Multiply((leftExpr, leftStr), (rightExpr, rightStr)) => ??? // TODO print (a² * a) as a³ 40 | case Square((_, str)) => s"$str²" 41 | } 42 | 43 | val expr: Fix[Expr] = 44 | multiply( 45 | square(sum(-3, 5)), 46 | sum(-3, 5) 47 | ) 48 | 49 | println(expr.para(algebra)) 50 | } 51 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex05/Ex05_Traverse.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex05 2 | 3 | import scalaz.Scalaz._ 4 | import scalaz._ 5 | 6 | trait Ex05_Traverse { 7 | // it's also a Functor[Expr] 8 | implicit val traverseExpr: Traverse[Expr] with Functor[Expr] = new Traverse[Expr] { 9 | 10 | override def traverseImpl[G[_], A, B](fa: Expr[A])(f: A => G[B])(implicit G: Applicative[G]): G[Expr[B]] = 11 | fa match { 12 | case IntValue(v) => G.point(IntValue(v)) 13 | case Sum(a1, a2) => (f(a1) ⊛ f(a2))(Sum.apply) 14 | case Multiply(a1, a2) => (f(a1) ⊛ f(a2))(Multiply.apply) 15 | case Square(a) => f(a).map(Square.apply) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex06/Ex06_Cofree.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex06 2 | 3 | import matryoshka.data._ 4 | import matryoshka._ 5 | import matryoshka.implicits._ 6 | import matryoshka.patterns._ 7 | import scalaz._ 8 | 9 | // -------------------- the DSL -------------------- 10 | sealed trait Expr[A] 11 | 12 | case class IntValue[A](v: Int) extends Expr[A] 13 | case class DecValue[A](v: Double) extends Expr[A] 14 | case class Sum[A](a: A, b: A) extends Expr[A] 15 | case class Square[A](a: A) extends Expr[A] 16 | 17 | sealed trait ExprType 18 | case object IntExpr extends ExprType 19 | case object DecExpr extends ExprType 20 | // ------------------------------------------------- 21 | 22 | object Ex06_Cofree extends App with Ex06_Traverse { 23 | 24 | // ---------- labelling expressions with Cofree 25 | 26 | val inferType: Algebra[Expr, Cofree[Expr, ExprType]] = { 27 | case IntValue(v) => Cofree.apply(IntExpr, IntValue(v)) // note that type order here is switched 28 | case DecValue(v) => Cofree.apply(DecExpr, DecValue(v)) 29 | case s @ Sum(a, b) => ??? // TODO 30 | case sq @ Square(a) => ??? // TODO 31 | } 32 | 33 | def evalToString(exp: Expr[String]): String = exp match { 34 | case IntValue(v) => v.toString 35 | case DecValue(v) => v.toString 36 | case Sum(d1, d2) => s"($d1 + $d2)" 37 | case Square(d) => s"($d²)" 38 | } 39 | 40 | val expr1: Fix[Expr] = 41 | sum( 42 | square(int(3)), 43 | sum(int(5), int(-20)) 44 | ) 45 | 46 | val expr2: Fix[Expr] = 47 | sum( 48 | square(int(3)), 49 | sum(int(5), dec(-20.2)) 50 | ) 51 | 52 | val typedExpr1: Cofree[Expr, ExprType] = expr1.cata(inferType) 53 | val typedExpr2: Cofree[Expr, ExprType] = expr2.cata(inferType) 54 | 55 | val toTypedStr: Algebra[EnvT[ExprType, Expr, ?], String] = { 56 | case EnvT((exprType, IntValue(v))) => s"($v): $exprType" 57 | case _ => ??? 58 | } 59 | 60 | println(typedExpr1.cata(toTypedStr)) 61 | println(typedExpr2.cata(toTypedStr)) 62 | } 63 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex06/Ex06_Traverse.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex06 2 | 3 | import scalaz._ 4 | import Scalaz._ 5 | import matryoshka.data.Fix 6 | import matryoshka.implicits._ 7 | 8 | trait Ex06_Traverse { 9 | // it's also a Functor[Expr] 10 | implicit val traverseExpr: Traverse[Expr] with Functor[Expr] = new Traverse[Expr] { 11 | 12 | override def traverseImpl[G[_], A, B](fa: Expr[A])(f: A => G[B])(implicit G: Applicative[G]): G[Expr[B]] = 13 | fa match { 14 | case IntValue(v) => G.point(IntValue(v)) 15 | case DecValue(v) => G.point(DecValue(v)) 16 | case Sum(a1, a2) => (f(a1) ⊛ f(a2))(Sum.apply) 17 | case Square(a) => f(a).map(Square.apply) 18 | } 19 | } 20 | 21 | def int(i: Int): Fix[Expr] = IntValue[Fix[Expr]](i).embed 22 | def dec(d: Double): Fix[Expr] = DecValue[Fix[Expr]](d).embed 23 | def sum(a: Int, b: Int): Fix[Expr] = Sum(int(a), int(b)).embed 24 | def sum(a: Fix[Expr], b: Fix[Expr]): Fix[Expr] = Sum(a, b).embed 25 | def square(e: Fix[Expr]): Fix[Expr] = Square(e).embed 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex07/Ex07_Histomorphism.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex07 2 | 3 | import matryoshka._ 4 | import matryoshka.data._ 5 | import matryoshka.implicits._ 6 | import scalaz._ 7 | 8 | // -------------------- the DSL -------------------- 9 | sealed trait Expr[A] 10 | 11 | case class IntValue[A](v: Int) extends Expr[A] 12 | case class DecValue[A](v: Double) extends Expr[A] 13 | case class Sum[A](a: A, b: A) extends Expr[A] 14 | case class Square[A](a: A) extends Expr[A] 15 | 16 | sealed trait ExprType 17 | case object IntExpr extends ExprType 18 | case object DecExpr extends ExprType 19 | // ------------------------------------------------- 20 | 21 | object Ex07_Histomorphism extends App with Ex07_Traverse { 22 | 23 | val expr: Fix[Expr] = 24 | sum( 25 | square(square(int(3))), 26 | square(square(square(int(5)))) 27 | ) 28 | 29 | // ----------------------- histomorphism 30 | val smartPrint: GAlgebra[Cofree[Expr, ?], Expr, String] = { 31 | case IntValue(v) => s"$v" 32 | case DecValue(v) => s"$v" 33 | case Sum(Cofree(a, _), Cofree(b, _)) => s"($a + $b)" 34 | case Square(Cofree(a, history)) => ??? // TODO For (x²)²)² print x^6 35 | } 36 | 37 | println(expr.histo(smartPrint)) 38 | } 39 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex07/Ex07_Traverse.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex07 2 | 3 | import scalaz.Scalaz._ 4 | import scalaz._ 5 | import matryoshka.data.Fix 6 | import matryoshka.implicits._ 7 | 8 | trait Ex07_Traverse { 9 | // it's also a Functor[Expr] 10 | implicit val traverseExpr: Traverse[Expr] with Functor[Expr] = new Traverse[Expr] { 11 | 12 | override def traverseImpl[G[_], A, B](fa: Expr[A])(f: A => G[B])(implicit G: Applicative[G]): G[Expr[B]] = 13 | fa match { 14 | case IntValue(v) => G.point(IntValue(v)) 15 | case DecValue(v) => G.point(DecValue(v)) 16 | case Sum(a1, a2) => (f(a1) ⊛ f(a2))(Sum.apply) 17 | case Square(a) => f(a).map(Square.apply) 18 | } 19 | } 20 | 21 | def int(i: Int): Fix[Expr] = IntValue[Fix[Expr]](i).embed 22 | def dec(d: Double): Fix[Expr] = DecValue[Fix[Expr]](d).embed 23 | def sum(a: Int, b: Int): Fix[Expr] = Sum(int(a), int(b)).embed 24 | def sum(a: Fix[Expr], b: Fix[Expr]): Fix[Expr] = Sum(a, b).embed 25 | def square(e: Fix[Expr]): Fix[Expr] = Square(e).embed 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/training/recursion/ex09/Ex09_Free.scala: -------------------------------------------------------------------------------- 1 | package training.recursion.ex09 2 | 3 | import matryoshka._ 4 | import matryoshka.data._ 5 | import matryoshka.implicits._ 6 | import scalaz.{Scalaz, _} 7 | import Scalaz._ 8 | 9 | // -------------------- the DSL -------------------- 10 | sealed trait CalcAction[A] 11 | 12 | case class IntValue(v: Int) extends CalcAction[Int] 13 | 14 | case class Sum(a: Int, b: Int) extends CalcAction[Int] 15 | 16 | case class Square(a: Int) extends CalcAction[Int] 17 | 18 | case class Draw(a: Int) extends CalcAction[Unit] 19 | 20 | // ------------------------------------------------- 21 | 22 | object Ex09_Free extends App { 23 | 24 | val program: Free[CalcAction, Unit] = for { 25 | i1 <- Free.point[CalcAction, Int](5) 26 | i2 <- Free.point[CalcAction, Int](1) 27 | sum <- Free.liftF(Sum(i1, i2): CalcAction[Int]) 28 | sq <- Free.liftF(Square(sum): CalcAction[Int]) 29 | _ <- Free.liftF(Draw(sq): CalcAction[Unit]) 30 | } yield () 31 | 32 | val interpreter: ~>[CalcAction, Id] = ??? 33 | 34 | program.foldMap(interpreter) // catamorphism 35 | } 36 | --------------------------------------------------------------------------------