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

Docs for Func and AppFunc #4378

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
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
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ possible:
* Travis Brown
* Trond Bjerkestrand
* Tya
* Ulises Torrella
* Valentin Willscher
* Valeriy Avanesov
* Valy Diarrassouba
Expand Down
13 changes: 12 additions & 1 deletion core/src/main/scala/cats/data/Func.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,18 @@ import cats.Contravariant
*/
sealed abstract class Func[F[_], A, B] { self =>
def run: A => F[B]

/**
* scala> val f = Func.func((x: Int) => List(x.toString))
* val f: cats.data.Func[List,Int,String] = ...
* scala> val g = f.map((x: String) => if (x=="0") None else Some(x))
* val g: cats.data.Func[List,Int,Option[String]] = ...
*/
def map[C](f: B => C)(implicit FF: Functor[F]): Func[F, A, C] =
Func.func(a => FF.map(self.run(a))(f))

/**
* Modify the context `F` using transformation `f`.
* Modify the context `F` using (natural) transformation `f`.
*/
def mapK[G[_]](f: F ~> G): Func[G, A, B] =
Func.func(a => f(run(a)))
Expand Down Expand Up @@ -118,6 +125,10 @@ sealed private[data] trait FuncApplicative[F[_], C] extends Applicative[λ[α =>

/**
* An implementation of [[Func]] that's specialized to [[Applicative]].
*
* As seen in [[https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf The Essence of the Iterator Pattern]]
* the Applicative Functor "capture the essence of the ITERATOR pattern"
* allowing to traverse and compose Applicative Functors
*/
sealed abstract class AppFunc[F[_], A, B] extends Func[F, A, B] { self =>
def F: Applicative[F]
Expand Down
78 changes: 78 additions & 0 deletions docs/datatypes/func.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Func and AppFunc

API Documentation: @:api(cats.data.Func)

Func is a wrapper around a `run` function `A => F[B]` where `F` is a functor. Given that, the Func data type is equipped with the known `map` function, and a `mapK` function to apply natural transformations (from a `Func[F[_], A, B]` get an `Func[G[_], A, B]`).

The signature `Func[F[_], A, B]` is very similar to the signature for [Kleisli](../datatypes/kleisli.md): `Kleisli[F[_], -A, B]`. The difference is that `Func` is a less restrictive data type that wraps around functors, and only provides basic methods `run`, `map`, and `mapK`, while `Kleisli` is strong enough to provide composition, flatMap, and more. We will see a more useful data type just next with `AppFunc`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does just [Kleisli] without an explicit link not 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.

Nope, I got an exception when running the preview. I got this explicit from another doc doing the same, must be for the same reason. Idk why it says the Klesli reference is "ambiguous"

Copy link
Contributor

Choose a reason for hiding this comment

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

Wierd. Too bad. Maybe 2 sections called "Kleisli" somewhere?


## Quick example

```scala mdoc:silent:nest
import cats.data.{ Func, AppFunc }
import cats._

val f: Func[List, Int, String] = Func.func((x: Int) => List(x.toString))

val g: Func[List, Int, Option[String]] = f.map((x: String) => if (x=="0") None else Some(x))

val optToList = new (Option ~> List) {
def apply[T](opt: Option[T]): List[T] =
opt.toList
}

// We transform the elements of List, of type
// Option[String] to List[String]
g.map(optToList(_))
// val res0: cats.data.Func[List,Int,List[String]] = ...
```



# AppFunc

AppFunc extends Func to wrap around a special type of functor: [Applicative] functors.

With applicative functors we can `compose`, form the `product`, and also `traverse` traversable functors

Signature: `AppFunc[F[_], A, B] extends Func[F, A, B]`

Now, for the reader familiar with [Kleisli](../datatypes/kleisli.md), we find an even more similar data type. `AppFunc` provides compositions of weaker constraint, allowing `AppFunc[F[_], A, B]` to be composed with `AppFunc[G[_], C, A]`.
## Composition

All of functional programming revolves around composing, and functors cannot be left behind. If we are working with multiple contexts we might want to compose them, for example: we want to `List` things, and discard some (`Option`).

To achieve this nested context behavior `AppFunc` uses the `Nested` datatype.
UlisesTorrella marked this conversation as resolved.
Show resolved Hide resolved

```scala mdoc:silent:nest
val appFuncOption: AppFunc[Option,Int,Int] = Func.appFunc((i: Int) => if (i==0) None else Some(i))

val appFuncList: AppFunc[List,Int,Int] = Func.appFunc((o: Int) => {List(o+1)})
(appFuncOption andThen appFuncList).run(1) //Nested(Some(List(2)))

(appFuncOption andThen appFuncList).run(0) //Nested(None)
// same thing with compose

(appFuncList compose appFuncOption)
```
## Product

Applicative functors, like monads, are closed under product. Cats models product of two applicative functors (they can be different!) in the @:api(cats.data.Tuple2K) data type.

For further reading: [herding cats](http://eed3si9n.com/herding-cats/combining-applicative.html#Product+of+applicative+functions)

```scala mdoc:silent:nest
(appFuncOption product appFuncList).run(1)
```
## Traverse

This explained in the implementation of the Applicative trait: [Applicative - Traverse](https://typelevel.org/cats/typeclasses/applicative.html#traverse)

```scala mdoc:silent:nest
appFuncOption.traverse(List(1,2,3))
// val res14: Option[List[Int]] = Some(List(1, 2, 3))

appFuncOption.traverse(List(1,2,0))
//val res15: Option[List[Int]] = None
```