From 23dbde2dec4ac44fffc98f540a24b0b5fd0e22fd Mon Sep 17 00:00:00 2001 From: zliu41 Date: Thu, 21 Sep 2017 20:40:15 -0700 Subject: [PATCH] Address comments --- docs/src/main/tut/typeclasses/arrow.md | 47 +++++++++++++------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/docs/src/main/tut/typeclasses/arrow.md b/docs/src/main/tut/typeclasses/arrow.md index 51696e2ae2b..e4842bc94bf 100644 --- a/docs/src/main/tut/typeclasses/arrow.md +++ b/docs/src/main/tut/typeclasses/arrow.md @@ -7,7 +7,7 @@ scaladoc: "#cats.arrow.Arrow" --- # Arrow -`Arrow` is a useful type class for modeling something that behaves like functions, such as `Function1`: `A => B`, `Kleisli`: `A => F[B]` (also known as `ReaderT`), `Cokleisli`: `F[A] => B`, etc. So useful, that Haskell provides special syntax (the `proc` notation) for composing and combining Arrows, similar as the `do` notation for sequencing monadic operations. +`Arrow` is a useful type class for modeling something that behaves like functions, such as `Function1`: `A => B`, `Kleisli`: `A => F[B]` (also known as `ReaderT`), `Cokleisli`: `F[A] => B`, etc. So useful, that Haskell provides special syntax (the `proc` notation) for composing and combining arrows, similar as the `do` notation for sequencing monadic operations. To create an `Arrow` instance for a type `F[A, B]`, the following abstract methods need to be implemented: @@ -21,27 +21,38 @@ def compose[A, B, C](f: F[B, C], g: F[A, B]): F[A, C] def first[A, B, C](fa: F[A, B]): F[(A, C), (B, C)] ``` -Once a type `F` has an `Arrow` instance, it gets for free a number of methods for composing and combining with other `Arrow`s. You will be able to do things like: -- Composing `fab: F[A, B]` and `fbc: F[B, C]` into `fac: F[A, C]` with `fab >>> fbc`. If `F` is `Function1` then `>>>` becomes alias of `andThen`. +Once a type `F` has an `Arrow` instance, it gets for free a number of methods for composing and combining multiple `Arrow`s. You will be able to do things like: +- Composing `fab: F[A, B]` and `fbc: F[B, C]` into `fac: F[A, C]` with `fab >>> fbc`. If `F` is `Function1` then `>>>` becomes an alias for `andThen`. - Taking two arrows `fab: F[A, B]` and `fbc: F[B, C]` and combining them into `F[(A, C) => (B, D)]` with `fab *** fbc`. The resulting arrow takes two inputs and processes them with two arrows, one for each input. - Taking two arrows `fab: F[A, B]` and `fac: F[A, C]` and combining them into `F[A => (B, C)]` with `fab &&& fbc`. The resulting arrow takes an input, duplicates it and processes each copy with a different arrow. - Taking an arrow `fab: F[A, B]` and turning it into `F[(C, A), (C, B)]` with `fab.second`. The resulting arrow takes two inputs, processes the second input and leaves the first input as it is. ## Example -As an example, let's create a fancy version of `Function1` that is capable of maintaining states. Let's call it `FancyFunction`: +As an example, let's create a fancy version of `Function1` called `FancyFunction`, that is capable of maintaining states. We then create an `Arrow` instance for `FancyFunction` and use it to compute the moving average of a list of numbers. ```tut:book:silent case class FancyFunction[A, B](run: A => (FancyFunction[A, B], B)) ``` -That is, given an `A`, it not only returns a `B`, but also returns a new `FancyFunction[A, B]`. This sounds similar as the `State` monad (which returns a result and a new `State` from an initial `State`), and indeed, `FancyFunction` can be used to perform stateful transformations. +That is, given an `A`, it not only returns a `B`, but also returns a new `FancyFunction[A, B]`. This sounds similar to the `State` monad (which returns a result and a new `State` from an initial `State`), and indeed, `FancyFunction` can be used to perform stateful transformations. For example, to run a stateful computation using a `FancyFunction` on a list of inputs, and collect the output into another list, we can define the following `runList` helper function: + +```tut:book:silent +def runList[A, B](ff: FancyFunction[A, B], as: List[A]): List[B] = as match { + case h :: t => + val (ff2, b) = ff.run(h) + b :: runList(ff2, t) + case _ => List() +} +``` + +In `runList`, the head element in `List[A]` is fed to `ff`, and each subsequent element is fed to a `FancyFunction` which is generated by running the previous `FancyFunction` on the previous element. If we have an `as: List[Int]`, and an `avg: FancyFunction[Int, Double]` which takes an integer and computes the average of all integers it has seen so far, we can call `runList(avg, as)` to get the list of moving average of `as`. + +Next let's create an `Arrow` instance for `FancyFunction` and see how to implement the `avg` arrow. -Here's an `Arrow` instance for `FancyFunction`: ```tut:book:silent import cats.arrow.Arrow -import cats.implicits._ implicit val arrowInstance: Arrow[FancyFunction] = new Arrow[FancyFunction] { @@ -63,7 +74,7 @@ implicit val arrowInstance: Arrow[FancyFunction] = new Arrow[FancyFunction] { ``` -Then we can start to do interesting things with it. For example, we can create a method `accum` that returns a `FancyFunction`, which accumulates values fed to it using the accumulation function `f` and the starting value `b`: +Once we have an `Arrow[FancyFunction]`, we can start to do interesting things with it. First, let's create a method `accum` that returns a `FancyFunction`, which accumulates values fed to it using the accumulation function `f` and the starting value `b`: ```tut:book:silent def accum[A, B](b: B)(f: (A, B) => B): FancyFunction[A, B] = FancyFunction {a => @@ -72,29 +83,18 @@ def accum[A, B](b: B)(f: (A, B) => B): FancyFunction[A, B] = FancyFunction {a => } ``` -We then define a method `runList` that takes a `List[A]`, feeds the `A` values one by one into the `FancyFunction` and collects the result into a `List[B]`: - -```tut:book:silent -def runList[A, B](ff: FancyFunction[A, B], as: List[A]): List[B] = as match { - case h :: t => - val (ff2, b) = ff.run(h) - b :: runList(ff2, t) - case _ => List() -} -``` - Now we can try it out: -```tut:book:silent +```tut:book runList(accum[Int, Int](0)(_ + _), List(6, 5, 4, 3, 2, 1)) -// res1: List[Int] = List(6, 11, 15, 18, 20, 21) ``` -As another example, let's make the above `FancyFunction` collect the running average of a list of numbers. To do so it needs to keep track of the count in addition to the sum. In this example we will combine several `FancyFunction`s to get the `FancyFunction` we want. +To make the aformentioned `avg` arrow, we need to keep track of both the count and the sum of the numbers we have seen so far. To do so, we will combine several `FancyFunction`s to get the `avg` arrow we want. We first define arrow `sum` in terms of `accum`, and define arrow `count` by composing `_ => 1` with `sum`: ```tut:book:silent +import cats.implicits._ import cats.kernel.Monoid def sum[A: Monoid]: FancyFunction[A, A] = accum(Monoid[A].empty)(_ |+| _) @@ -113,7 +113,6 @@ def avg: FancyFunction[Int, Double] = And try it out: -```tut:book:silent +```tut:book runList(avg, List(1, 10, 100, 1000)) -// res2: List[Int] = List(1.0, 5.5, 37.0, 277.75) ```