Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add docs for Arrow #1924

Merged
merged 5 commits into from
Sep 29, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/src/main/resources/microsite/data/menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ options:
url: typeclasses/show.html
menu_type: typeclasses

- title: Arrow
url: typeclasses/arrow.html
menu_type: typeclasses

###########################
# Data Types Menu Options #
###########################
Expand Down
119 changes: 119 additions & 0 deletions docs/src/main/tut/typeclasses/arrow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
layout: docs
title: "Arrow"
section: "typeclasses"
source: "core/src/main/scala/cats/arrow/Arrow.scala"
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.
Copy link
Member

@LukaJCB LukaJCB Sep 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "similar to" would be better than "similar as" :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. I can be pretty bad at choosing the right preposition :)


To create an `Arrow` instance for a type `F[A, B]`, the following abstract methods need to be implemented:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be helpful to show a few examples of using Arrow methods on instances that already exist (such as Function1 before diving into what's needed to create your own custom Arrow instance. It's very rare that you'll be creating your own custom Arrow instance, but more common that people will want to take advantage of some of the methods on existing types.


```
def lift[A, B](f: A => B): F[A, B]

def id[A]: F[A, A]

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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be "becomes an alias for" :)

- 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is related to the previous comment, but I think that some concrete examples might help these explanations.

- 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`:

```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.

Here's an `Arrow` instance for `FancyFunction`:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind giving an example of use of FancyFunction before showing the Arrow instance implementation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. There's now an example of how to use avg, before showing the Arrow instance for FancyFunction and explaining how to implement avg.


```tut:book:silent
import cats.arrow.Arrow
import cats.implicits._

implicit val arrowInstance: Arrow[FancyFunction] = new Arrow[FancyFunction] {

override def lift[A, B](f: A => B): FancyFunction[A, B] = FancyFunction(lift(f) -> f(_))

override def first[A, B, C](fa: FancyFunction[A, B]): FancyFunction[(A, C), (B, C)] = FancyFunction {case (a, c) =>
val (fa2, b) = fa.run(a)
(first(fa2), (b, c))
}

override def id[A]: FancyFunction[A, A] = FancyFunction(id -> _)

override def compose[A, B, C](f: FancyFunction[B, C], g: FancyFunction[A, B]): FancyFunction[A, C] = FancyFunction {a =>
val (gg, b) = g.run(a)
val (ff, c) = f.run(b)
(compose(ff, gg), c)
}
}

```

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`:

```tut:book:silent
def accum[A, B](b: B)(f: (A, B) => B): FancyFunction[A, B] = FancyFunction {a =>
val b2 = f(a, b)
(accum(b2)(f), b2)
}
```

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
runList(accum[Int, Int](0)(_ + _), List(6, 5, 4, 3, 2, 1))
// res1: List[Int] = List(6, 11, 15, 18, 20, 21)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why use tut:silent and manually write the output? Tut's output won't work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was fixed in the 2nd commit. First time using tut..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not make this non-silent? Then we could remove the comment, or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I'm not super familiar with tut.

```

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.

We first define arrow `sum` in terms of `accum`, and define arrow `count` by composing `_ => 1` with `sum`:

```tut:book:silent
import cats.kernel.Monoid

def sum[A: Monoid]: FancyFunction[A, A] = accum(Monoid[A].empty)(_ |+| _)
def count[A]: FancyFunction[A, Int] = Arrow[FancyFunction].lift((_: A) => 1) >>> sum
```

Then we define a `combine` function that takes an input and processes two copies of it with two arrows, and finally, the `avg` arrow in terms of the arrows we have so far:

```tut:book:silent
def combine[F[_, _]: Arrow, A, B, C](fab: F[A, B], fac: F[A, C]): F[A, (B, C)] =
Arrow[F].lift((a: A) => (a, a)) >>> (fab *** fac)

def avg: FancyFunction[Int, Double] =
combine(sum[Int], count[Int]) >>> Arrow[FancyFunction].lift{case (x, y) => x.toDouble / y}
```

And try it out:

```tut:book:silent
runList(avg, List(1, 10, 100, 1000))
// res2: List[Int] = List(1.0, 5.5, 37.0, 277.75)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. Why not just tut:book?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

```