From deb9ee394272dc405f5bb8089d3d3af29ab36f00 Mon Sep 17 00:00:00 2001 From: Ulises Date: Mon, 16 Jan 2023 15:44:10 +0100 Subject: [PATCH 01/15] a bit of comentary on data.Func --- core/src/main/scala/cats/data/Func.scala | 18 +- docs/datatypes/func.md | 304 +++++++++++++++++++++++ 2 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 docs/datatypes/func.md diff --git a/core/src/main/scala/cats/data/Func.scala b/core/src/main/scala/cats/data/Func.scala index 9da90104df..4127b3621b 100644 --- a/core/src/main/scala/cats/data/Func.scala +++ b/core/src/main/scala/cats/data/Func.scala @@ -31,11 +31,23 @@ import cats.Contravariant */ sealed abstract class Func[F[_], A, B] { self => def run: A => F[B] + + /** + * Composition of Functors, if we have a mapping from A => F[B] + * and one morphism from B => C we can define a new functor that + * consist on applying A => F[B] mapping, F[B] => F[C] using B => C + * then compose A => F[B] ∘ F[B] => F[C] + * + * scala> val firstFunc = Func.func((x: Int) => List(x.toString)) + * val firstFunc: cats.data.Func[List,Int,String] = ... + * scala> val secondFunc = firstFunc.map((x: String) => if (x=="0") None else Some(x)) + * val secondFunc: 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))) @@ -118,6 +130,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] diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md new file mode 100644 index 0000000000..d40dd85884 --- /dev/null +++ b/docs/datatypes/func.md @@ -0,0 +1,304 @@ +# Func + +Func is a data that abstract the +Kleisli enables composition of functions that return a monadic value, for instance an `Option[Int]` +or a `Either[String, List[Double]]`, without having functions take an `Option` or `Either` as a parameter, +which can be strange and unwieldy. + +We may also have several functions which depend on some environment and want a nice way to compose these functions +to ensure they all receive the same environment. Or perhaps we have functions which depend on their own "local" +configuration and all the configurations together make up a "global" application configuration. How do we +have these functions play nice with each other despite each only knowing about their own local requirements? + +These situations are where `Kleisli` is immensely helpful. + +## Functions +One of the most useful properties of functions is that they **compose**. That is, given a function +`A => B` and a function `B => C`, we can combine them to create a new function `A => C`. It is through +this compositional property that we are able to write many small functions and compose them together +to create a larger one that suits our needs. + +```scala mdoc:silent +val twice: Int => Int = + x => x * 2 + +val countCats: Int => String = + x => if (x == 1) "1 cat" else s"$x cats" + +val twiceAsManyCats: Int => String = + twice andThen countCats // equivalent to: countCats compose twice +``` + +Thus. + +```scala mdoc +twiceAsManyCats(1) // "2 cats" +``` + +Sometimes, our functions will need to return monadic values. For instance, consider the following set of functions. + +```scala mdoc:silent +val parse: String => Option[Int] = + s => if (s.matches("-?[0-9]+")) Some(s.toInt) else None + +val reciprocal: Int => Option[Double] = + i => if (i != 0) Some(1.0 / i) else None +``` + +As it stands we cannot use `Function1.compose` (or `Function1.andThen`) to compose these two functions. +The output type of `parse` is `Option[Int]` whereas the input type of `reciprocal` is `Int`. + +This is where `Kleisli` comes into play. + +## Kleisli +At its core, `Kleisli[F[_], A, B]` is just a wrapper around the function `A => F[B]`. Depending on the +properties of the `F[_]`, we can do different things with `Kleisli`s. For instance, if `F[_]` has a +`FlatMap[F]` instance (we can call `flatMap` on `F[A]` values), we can +compose two `Kleisli`s much like we can two functions. + +```scala mdoc:silent +import cats.FlatMap +import cats.implicits._ + +final case class Kleisli[F[_], A, B](run: A => F[B]) { + def compose[Z](k: Kleisli[F, Z, A])(implicit F: FlatMap[F]): Kleisli[F, Z, B] = + Kleisli[F, Z, B](z => k.run(z).flatMap(run)) +} +``` + +Returning to our earlier example: + +```scala mdoc:silent:nest +// Bring in cats.FlatMap[Option] instance +import cats.implicits._ + +val parse: Kleisli[Option,String,Int] = + Kleisli((s: String) => if (s.matches("-?[0-9]+")) Some(s.toInt) else None) + +val reciprocal: Kleisli[Option,Int,Double] = + Kleisli((i: Int) => if (i != 0) Some(1.0 / i) else None) + +val parseAndReciprocal: Kleisli[Option,String,Double] = + reciprocal.compose(parse) +``` + +`Kleisli#andThen` can be defined similarly. + +It is important to note that the `F[_]` having a `FlatMap` (or a `Monad`) instance is not a hard requirement - +we can do useful things with weaker requirements. Such an example would be `Kleisli#map`, which only requires +that `F[_]` have a `Functor` instance (e.g. is equipped with `map: F[A] => (A => B) => F[B]`). + +```scala mdoc:silent:nest +import cats.Functor + +final case class Kleisli[F[_], A, B](run: A => F[B]) { + def map[C](f: B => C)(implicit F: Functor[F]): Kleisli[F, A, C] = + Kleisli[F, A, C](a => F.map(run(a))(f)) +} +``` + +Below are some more methods on `Kleisli` that can be used as long as the constraint on `F[_]` +is satisfied. + +``` +Method | Constraint on `F[_]` +--------- | ------------------- +andThen | FlatMap +compose | FlatMap +flatMap | FlatMap +lower | Monad +map | Functor +traverse | Applicative +``` + +### Type class instances +The type class instances for `Kleisli`, like that for functions, often fix the input type (and the `F[_]`) and leave +the output type free. What type class instances it has tends to depend on what instances the `F[_]` has. For +instance, `Kleisli[F, A, B]` has a `Functor` instance as long as the chosen `F[_]` does. It has a `Monad` +instance as long as the chosen `F[_]` does. The instances in Cats are laid out in a way such that implicit +resolution will pick up the most specific instance it can (depending on the `F[_]`). + +An example of a `Monad` instance for `Kleisli` is shown below. + +*Note*: the example below assumes usage of the [kind-projector compiler plugin](https://github.com/typelevel/kind-projector) and will not compile if it is not being used in a project. + +```scala mdoc:silent +import cats.implicits._ + +// We can define a FlatMap instance for Kleisli if the F[_] we chose has a FlatMap instance +// Note the input type and F are fixed, with the output type left free +implicit def kleisliFlatMap[F[_], Z](implicit F: FlatMap[F]): FlatMap[Kleisli[F, Z, *]] = + new FlatMap[Kleisli[F, Z, *]] { + def flatMap[A, B](fa: Kleisli[F, Z, A])(f: A => Kleisli[F, Z, B]): Kleisli[F, Z, B] = + Kleisli(z => fa.run(z).flatMap(a => f(a).run(z))) + + def map[A, B](fa: Kleisli[F, Z, A])(f: A => B): Kleisli[F, Z, B] = + Kleisli(z => fa.run(z).map(f)) + + def tailRecM[A, B](a: A)(f: A => Kleisli[F, Z, Either[A, B]]) = + Kleisli[F, Z, B]({ z => FlatMap[F].tailRecM(a) { f(_).run(z) } }) + } +``` + +Below is a table of some of the type class instances `Kleisli` can have depending on what instances `F[_]` has. + +``` +Type class | Constraint on `F[_]` +-------------- | ------------------- +Functor | Functor +Apply | Apply +Applicative | Applicative +FlatMap | FlatMap +Monad | Monad +Arrow | Monad +Split | FlatMap +Strong | Functor +SemigroupK* | FlatMap +MonoidK* | Monad +``` + +*These instances only exist for Kleisli arrows with identical input and output types; that is, +`Kleisli[F, A, A]` for some type A. These instances use Kleisli composition as the `combine` operation, +and `Monad.pure` as the `empty` value. + +Also, there is an instance of `Monoid[Kleisli[F, A, B]]` if there is an instance of `Monoid[F[B]]`. +`Monoid.combine` here creates a new Kleisli arrow which takes an `A` value and feeds it into each +of the combined Kleisli arrows, which together return two `F[B]` values. Then, they are combined into one +using the `Monoid[F[B]]` instance. + +## Other uses +### Monad Transformers +Many data types have a monad transformer equivalent that allows us to compose the `Monad` instance of the data +type with any other `Monad` instance. For instance, `OptionT[F[_], A]` allows us to compose the monadic properties +of `Option` with any other `F[_]`, such as a `List`. This allows us to work with nested contexts/effects in a +nice way (for example, in for-comprehensions). + +`Kleisli` can be viewed as the monad transformer for functions. Recall that at its essence, `Kleisli[F, A, B]` +is just a function `A => F[B]`, with niceties to make working with the value we actually care about, the `B`, easy. +`Kleisli` allows us to take the effects of functions and have them play nice with the effects of any other `F[_]`. + +This may raise the question, what exactly is the "effect" of a function? + +Well, if we take a look at any function, we can see it takes some input and produces some output with it, without +having touched the input (assuming the function is pure, i.e. +[referentially transparent](https://en.wikipedia.org/wiki/Referential_transparency_%28computer_science%29)). +That is, we take a read-only value, and produce some value with it. For this reason, the type class instances for +functions often refer to the function as a `Reader`. For instance, it is common to hear about the `Reader` monad. +In the same spirit, Cats defines a `Reader` type alias along the lines of: + +```scala mdoc:silent +// We want A => B, but Kleisli provides A => F[B]. To make the types/shapes match, +// we need an F[_] such that providing it a type A is equivalent to A +// This can be thought of as the type-level equivalent of the identity function +type Id[A] = A + +type Reader[A, B] = Kleisli[Id, A, B] +object Reader { + // Lifts a plain function A => B into a Kleisli, giving us access + // to all the useful methods and type class instances + def apply[A, B](f: A => B): Reader[A, B] = Kleisli[Id, A, B](f) +} + +type ReaderT[F[_], A, B] = Kleisli[F, A, B] +val ReaderT = Kleisli +``` + +The `ReaderT` value alias exists to allow users to use the `Kleisli` companion object as if it were `ReaderT`, if +they were so inclined. + +The topic of functions as a read-only environment brings us to our next common use case of `Kleisli` - configuration. + +### Configuration +Functional programming advocates the creation of programs and modules by composing smaller, simpler modules. This +philosophy intentionally mirrors that of function composition - write many small functions, and compose them +to build larger ones. After all, our programs are just functions. + +Let's look at some example modules, where each module has its own configuration that is validated by a function. +If the configuration is good, we return a `Some` of the module, otherwise a `None`. This example uses `Option` for +simplicity - if you want to provide error messages or other failure context, consider using `Either` instead. + +```scala mdoc:silent +case class DbConfig(url: String, user: String, pass: String) +trait Db +object Db { + val fromDbConfig: Kleisli[Option, DbConfig, Db] = ??? +} + +case class ServiceConfig(addr: String, port: Int) +trait Service +object Service { + val fromServiceConfig: Kleisli[Option, ServiceConfig, Service] = ??? +} +``` + +We have two independent modules, a `Db` (allowing access to a database) and a `Service` (supporting an API to provide +data over the web). Both depend on their own configuration parameters. Neither know or care about the other, as it +should be. However our application needs both of these modules to work. It is plausible we then have a more global +application configuration. + +```scala mdoc:silent +case class AppConfig(dbConfig: DbConfig, serviceConfig: ServiceConfig) + +class App(db: Db, service: Service) +``` + +As it stands, we cannot use both `Kleisli` validation functions together nicely - one takes a `DbConfig`, the +other a `ServiceConfig`. That means the `FlatMap` (and by extension, the `Monad`) instances differ (recall the +input type is fixed in the type class instances). However, there is a nice function on `Kleisli` called `local`. + +```scala mdoc:silent:nest +final case class Kleisli[F[_], A, B](run: A => F[B]) { + def local[AA](f: AA => A): Kleisli[F, AA, B] = Kleisli(f.andThen(run)) +} +``` + +What `local` allows us to do is essentially "expand" our input type to a more "general" one. In our case, we +can take a `Kleisli` that expects a `DbConfig` or `ServiceConfig` and turn it into one that expects an `AppConfig`, +as long as we tell it how to go from an `AppConfig` to the other configs. + +Now we can create our application config validator! + +```scala mdoc:silent:nest +final case class Kleisli[F[_], Z, A](run: Z => F[A]) { + def flatMap[B](f: A => Kleisli[F, Z, B])(implicit F: FlatMap[F]): Kleisli[F, Z, B] = + Kleisli(z => F.flatMap(run(z))(a => f(a).run(z))) + + def map[B](f: A => B)(implicit F: Functor[F]): Kleisli[F, Z, B] = + Kleisli(z => F.map(run(z))(f)) + + def local[ZZ](f: ZZ => Z): Kleisli[F, ZZ, A] = Kleisli(f.andThen(run)) +} + +case class DbConfig(url: String, user: String, pass: String) +trait Db +object Db { + val fromDbConfig: Kleisli[Option, DbConfig, Db] = ??? +} + +case class ServiceConfig(addr: String, port: Int) +trait Service +object Service { + val fromServiceConfig: Kleisli[Option, ServiceConfig, Service] = ??? +} + +case class AppConfig(dbConfig: DbConfig, serviceConfig: ServiceConfig) + +class App(db: Db, service: Service) + +def appFromAppConfig: Kleisli[Option, AppConfig, App] = + for { + db <- Db.fromDbConfig.local[AppConfig](_.dbConfig) + sv <- Service.fromServiceConfig.local[AppConfig](_.serviceConfig) + } yield new App(db, sv) +``` + +What if we need a module that doesn't need any config validation, say a strategy to log events? We would have such a +module be instantiated from a config directly, without an `Option` - we would have something like +`Kleisli[Id, LogConfig, Log]` (alternatively, `Reader[LogConfig, Log]`). However, this won't play nice with our other +`Kleisli`s since those use `Option` instead of `Id`. + +We can define a `lift` method on `Kleisli` (available already on `Kleisli` in Cats) that takes a type parameter `G[_]` +such that `G` has an `Applicative` instance and lifts a `Kleisli` value such that its output type is `G[F[B]]`. This +allows us to then lift a `Reader[A, B]` into a `Kleisli[G, A, B]`. Note that lifting a `Reader[A, B]` into some `G[_]` +is equivalent to having a `Kleisli[G, A, B]` since `Reader[A, B]` is just a type alias for `Kleisli[Id, A, B]`, and +`type Id[A] = A` so `G[Id[A]]` is equivalent to `G[A]`. From 7954176ae3bde26036529b63db430cccef187d47 Mon Sep 17 00:00:00 2001 From: Ulises Date: Tue, 17 Jan 2023 17:17:14 +0100 Subject: [PATCH 02/15] first draft --- docs/datatypes/func.md | 302 ++--------------------------------------- 1 file changed, 8 insertions(+), 294 deletions(-) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index d40dd85884..b37c57bce1 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -1,304 +1,18 @@ # Func -Func is a data that abstract the -Kleisli enables composition of functions that return a monadic value, for instance an `Option[Int]` -or a `Either[String, List[Double]]`, without having functions take an `Option` or `Either` as a parameter, -which can be strange and unwieldy. +Func[F[_], A, B] generalizes functors A=>F[B]. Equiped with a map function, and a `run` function that lifts values from A to F[B]. -We may also have several functions which depend on some environment and want a nice way to compose these functions -to ensure they all receive the same environment. Or perhaps we have functions which depend on their own "local" -configuration and all the configurations together make up a "global" application configuration. How do we -have these functions play nice with each other despite each only knowing about their own local requirements? +We can also apply natural transformations to a Func using the equiped function `mapK`. -These situations are where `Kleisli` is immensely helpful. +# AppFunc -## Functions -One of the most useful properties of functions is that they **compose**. That is, given a function -`A => B` and a function `B => C`, we can combine them to create a new function `A => C`. It is through -this compositional property that we are able to write many small functions and compose them together -to create a larger one that suits our needs. +Applicative functors can be composed, can traverse traversable functors, and we can obtain the product. -```scala mdoc:silent -val twice: Int => Int = - x => x * 2 +## Composition -val countCats: Int => String = - x => if (x == 1) "1 cat" else s"$x cats" +Compose and andThen -val twiceAsManyCats: Int => String = - twice andThen countCats // equivalent to: countCats compose twice -``` +## Product -Thus. +## Traverse -```scala mdoc -twiceAsManyCats(1) // "2 cats" -``` - -Sometimes, our functions will need to return monadic values. For instance, consider the following set of functions. - -```scala mdoc:silent -val parse: String => Option[Int] = - s => if (s.matches("-?[0-9]+")) Some(s.toInt) else None - -val reciprocal: Int => Option[Double] = - i => if (i != 0) Some(1.0 / i) else None -``` - -As it stands we cannot use `Function1.compose` (or `Function1.andThen`) to compose these two functions. -The output type of `parse` is `Option[Int]` whereas the input type of `reciprocal` is `Int`. - -This is where `Kleisli` comes into play. - -## Kleisli -At its core, `Kleisli[F[_], A, B]` is just a wrapper around the function `A => F[B]`. Depending on the -properties of the `F[_]`, we can do different things with `Kleisli`s. For instance, if `F[_]` has a -`FlatMap[F]` instance (we can call `flatMap` on `F[A]` values), we can -compose two `Kleisli`s much like we can two functions. - -```scala mdoc:silent -import cats.FlatMap -import cats.implicits._ - -final case class Kleisli[F[_], A, B](run: A => F[B]) { - def compose[Z](k: Kleisli[F, Z, A])(implicit F: FlatMap[F]): Kleisli[F, Z, B] = - Kleisli[F, Z, B](z => k.run(z).flatMap(run)) -} -``` - -Returning to our earlier example: - -```scala mdoc:silent:nest -// Bring in cats.FlatMap[Option] instance -import cats.implicits._ - -val parse: Kleisli[Option,String,Int] = - Kleisli((s: String) => if (s.matches("-?[0-9]+")) Some(s.toInt) else None) - -val reciprocal: Kleisli[Option,Int,Double] = - Kleisli((i: Int) => if (i != 0) Some(1.0 / i) else None) - -val parseAndReciprocal: Kleisli[Option,String,Double] = - reciprocal.compose(parse) -``` - -`Kleisli#andThen` can be defined similarly. - -It is important to note that the `F[_]` having a `FlatMap` (or a `Monad`) instance is not a hard requirement - -we can do useful things with weaker requirements. Such an example would be `Kleisli#map`, which only requires -that `F[_]` have a `Functor` instance (e.g. is equipped with `map: F[A] => (A => B) => F[B]`). - -```scala mdoc:silent:nest -import cats.Functor - -final case class Kleisli[F[_], A, B](run: A => F[B]) { - def map[C](f: B => C)(implicit F: Functor[F]): Kleisli[F, A, C] = - Kleisli[F, A, C](a => F.map(run(a))(f)) -} -``` - -Below are some more methods on `Kleisli` that can be used as long as the constraint on `F[_]` -is satisfied. - -``` -Method | Constraint on `F[_]` ---------- | ------------------- -andThen | FlatMap -compose | FlatMap -flatMap | FlatMap -lower | Monad -map | Functor -traverse | Applicative -``` - -### Type class instances -The type class instances for `Kleisli`, like that for functions, often fix the input type (and the `F[_]`) and leave -the output type free. What type class instances it has tends to depend on what instances the `F[_]` has. For -instance, `Kleisli[F, A, B]` has a `Functor` instance as long as the chosen `F[_]` does. It has a `Monad` -instance as long as the chosen `F[_]` does. The instances in Cats are laid out in a way such that implicit -resolution will pick up the most specific instance it can (depending on the `F[_]`). - -An example of a `Monad` instance for `Kleisli` is shown below. - -*Note*: the example below assumes usage of the [kind-projector compiler plugin](https://github.com/typelevel/kind-projector) and will not compile if it is not being used in a project. - -```scala mdoc:silent -import cats.implicits._ - -// We can define a FlatMap instance for Kleisli if the F[_] we chose has a FlatMap instance -// Note the input type and F are fixed, with the output type left free -implicit def kleisliFlatMap[F[_], Z](implicit F: FlatMap[F]): FlatMap[Kleisli[F, Z, *]] = - new FlatMap[Kleisli[F, Z, *]] { - def flatMap[A, B](fa: Kleisli[F, Z, A])(f: A => Kleisli[F, Z, B]): Kleisli[F, Z, B] = - Kleisli(z => fa.run(z).flatMap(a => f(a).run(z))) - - def map[A, B](fa: Kleisli[F, Z, A])(f: A => B): Kleisli[F, Z, B] = - Kleisli(z => fa.run(z).map(f)) - - def tailRecM[A, B](a: A)(f: A => Kleisli[F, Z, Either[A, B]]) = - Kleisli[F, Z, B]({ z => FlatMap[F].tailRecM(a) { f(_).run(z) } }) - } -``` - -Below is a table of some of the type class instances `Kleisli` can have depending on what instances `F[_]` has. - -``` -Type class | Constraint on `F[_]` --------------- | ------------------- -Functor | Functor -Apply | Apply -Applicative | Applicative -FlatMap | FlatMap -Monad | Monad -Arrow | Monad -Split | FlatMap -Strong | Functor -SemigroupK* | FlatMap -MonoidK* | Monad -``` - -*These instances only exist for Kleisli arrows with identical input and output types; that is, -`Kleisli[F, A, A]` for some type A. These instances use Kleisli composition as the `combine` operation, -and `Monad.pure` as the `empty` value. - -Also, there is an instance of `Monoid[Kleisli[F, A, B]]` if there is an instance of `Monoid[F[B]]`. -`Monoid.combine` here creates a new Kleisli arrow which takes an `A` value and feeds it into each -of the combined Kleisli arrows, which together return two `F[B]` values. Then, they are combined into one -using the `Monoid[F[B]]` instance. - -## Other uses -### Monad Transformers -Many data types have a monad transformer equivalent that allows us to compose the `Monad` instance of the data -type with any other `Monad` instance. For instance, `OptionT[F[_], A]` allows us to compose the monadic properties -of `Option` with any other `F[_]`, such as a `List`. This allows us to work with nested contexts/effects in a -nice way (for example, in for-comprehensions). - -`Kleisli` can be viewed as the monad transformer for functions. Recall that at its essence, `Kleisli[F, A, B]` -is just a function `A => F[B]`, with niceties to make working with the value we actually care about, the `B`, easy. -`Kleisli` allows us to take the effects of functions and have them play nice with the effects of any other `F[_]`. - -This may raise the question, what exactly is the "effect" of a function? - -Well, if we take a look at any function, we can see it takes some input and produces some output with it, without -having touched the input (assuming the function is pure, i.e. -[referentially transparent](https://en.wikipedia.org/wiki/Referential_transparency_%28computer_science%29)). -That is, we take a read-only value, and produce some value with it. For this reason, the type class instances for -functions often refer to the function as a `Reader`. For instance, it is common to hear about the `Reader` monad. -In the same spirit, Cats defines a `Reader` type alias along the lines of: - -```scala mdoc:silent -// We want A => B, but Kleisli provides A => F[B]. To make the types/shapes match, -// we need an F[_] such that providing it a type A is equivalent to A -// This can be thought of as the type-level equivalent of the identity function -type Id[A] = A - -type Reader[A, B] = Kleisli[Id, A, B] -object Reader { - // Lifts a plain function A => B into a Kleisli, giving us access - // to all the useful methods and type class instances - def apply[A, B](f: A => B): Reader[A, B] = Kleisli[Id, A, B](f) -} - -type ReaderT[F[_], A, B] = Kleisli[F, A, B] -val ReaderT = Kleisli -``` - -The `ReaderT` value alias exists to allow users to use the `Kleisli` companion object as if it were `ReaderT`, if -they were so inclined. - -The topic of functions as a read-only environment brings us to our next common use case of `Kleisli` - configuration. - -### Configuration -Functional programming advocates the creation of programs and modules by composing smaller, simpler modules. This -philosophy intentionally mirrors that of function composition - write many small functions, and compose them -to build larger ones. After all, our programs are just functions. - -Let's look at some example modules, where each module has its own configuration that is validated by a function. -If the configuration is good, we return a `Some` of the module, otherwise a `None`. This example uses `Option` for -simplicity - if you want to provide error messages or other failure context, consider using `Either` instead. - -```scala mdoc:silent -case class DbConfig(url: String, user: String, pass: String) -trait Db -object Db { - val fromDbConfig: Kleisli[Option, DbConfig, Db] = ??? -} - -case class ServiceConfig(addr: String, port: Int) -trait Service -object Service { - val fromServiceConfig: Kleisli[Option, ServiceConfig, Service] = ??? -} -``` - -We have two independent modules, a `Db` (allowing access to a database) and a `Service` (supporting an API to provide -data over the web). Both depend on their own configuration parameters. Neither know or care about the other, as it -should be. However our application needs both of these modules to work. It is plausible we then have a more global -application configuration. - -```scala mdoc:silent -case class AppConfig(dbConfig: DbConfig, serviceConfig: ServiceConfig) - -class App(db: Db, service: Service) -``` - -As it stands, we cannot use both `Kleisli` validation functions together nicely - one takes a `DbConfig`, the -other a `ServiceConfig`. That means the `FlatMap` (and by extension, the `Monad`) instances differ (recall the -input type is fixed in the type class instances). However, there is a nice function on `Kleisli` called `local`. - -```scala mdoc:silent:nest -final case class Kleisli[F[_], A, B](run: A => F[B]) { - def local[AA](f: AA => A): Kleisli[F, AA, B] = Kleisli(f.andThen(run)) -} -``` - -What `local` allows us to do is essentially "expand" our input type to a more "general" one. In our case, we -can take a `Kleisli` that expects a `DbConfig` or `ServiceConfig` and turn it into one that expects an `AppConfig`, -as long as we tell it how to go from an `AppConfig` to the other configs. - -Now we can create our application config validator! - -```scala mdoc:silent:nest -final case class Kleisli[F[_], Z, A](run: Z => F[A]) { - def flatMap[B](f: A => Kleisli[F, Z, B])(implicit F: FlatMap[F]): Kleisli[F, Z, B] = - Kleisli(z => F.flatMap(run(z))(a => f(a).run(z))) - - def map[B](f: A => B)(implicit F: Functor[F]): Kleisli[F, Z, B] = - Kleisli(z => F.map(run(z))(f)) - - def local[ZZ](f: ZZ => Z): Kleisli[F, ZZ, A] = Kleisli(f.andThen(run)) -} - -case class DbConfig(url: String, user: String, pass: String) -trait Db -object Db { - val fromDbConfig: Kleisli[Option, DbConfig, Db] = ??? -} - -case class ServiceConfig(addr: String, port: Int) -trait Service -object Service { - val fromServiceConfig: Kleisli[Option, ServiceConfig, Service] = ??? -} - -case class AppConfig(dbConfig: DbConfig, serviceConfig: ServiceConfig) - -class App(db: Db, service: Service) - -def appFromAppConfig: Kleisli[Option, AppConfig, App] = - for { - db <- Db.fromDbConfig.local[AppConfig](_.dbConfig) - sv <- Service.fromServiceConfig.local[AppConfig](_.serviceConfig) - } yield new App(db, sv) -``` - -What if we need a module that doesn't need any config validation, say a strategy to log events? We would have such a -module be instantiated from a config directly, without an `Option` - we would have something like -`Kleisli[Id, LogConfig, Log]` (alternatively, `Reader[LogConfig, Log]`). However, this won't play nice with our other -`Kleisli`s since those use `Option` instead of `Id`. - -We can define a `lift` method on `Kleisli` (available already on `Kleisli` in Cats) that takes a type parameter `G[_]` -such that `G` has an `Applicative` instance and lifts a `Kleisli` value such that its output type is `G[F[B]]`. This -allows us to then lift a `Reader[A, B]` into a `Kleisli[G, A, B]`. Note that lifting a `Reader[A, B]` into some `G[_]` -is equivalent to having a `Kleisli[G, A, B]` since `Reader[A, B]` is just a type alias for `Kleisli[Id, A, B]`, and -`type Id[A] = A` so `G[Id[A]]` is equivalent to `G[A]`. From e0999bf9c86c27d3b4ae7dd42f20a361114351f1 Mon Sep 17 00:00:00 2001 From: Ulises Date: Wed, 18 Jan 2023 18:16:47 +0100 Subject: [PATCH 03/15] func and appfunc datatype docs --- core/src/main/scala/cats/data/Func.scala | 15 ++---- docs/datatypes/func.md | 62 ++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/core/src/main/scala/cats/data/Func.scala b/core/src/main/scala/cats/data/Func.scala index 4127b3621b..4620a840c1 100644 --- a/core/src/main/scala/cats/data/Func.scala +++ b/core/src/main/scala/cats/data/Func.scala @@ -32,16 +32,11 @@ import cats.Contravariant sealed abstract class Func[F[_], A, B] { self => def run: A => F[B] - /** - * Composition of Functors, if we have a mapping from A => F[B] - * and one morphism from B => C we can define a new functor that - * consist on applying A => F[B] mapping, F[B] => F[C] using B => C - * then compose A => F[B] ∘ F[B] => F[C] - * - * scala> val firstFunc = Func.func((x: Int) => List(x.toString)) - * val firstFunc: cats.data.Func[List,Int,String] = ... - * scala> val secondFunc = firstFunc.map((x: String) => if (x=="0") None else Some(x)) - * val secondFunc: cats.data.Func[List,Int,Option[String]] = ... + /** + * 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)) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index b37c57bce1..b4e543f1bf 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -1,18 +1,72 @@ # Func -Func[F[_], A, B] generalizes functors A=>F[B]. Equiped with a map function, and a `run` function that lifts values from A to F[B]. +Func is a wrapper arround a `run` function `a => F[B]` where F is a functor. Given that, the Func data type is equiped with the known `map` function, and a `mapK` function to apply natural transformations (from a `F` Func get an `G` Func). + +## 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 natTransformation = 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(natTransformation(_)) +``` + -We can also apply natural transformations to a Func using the equiped function `mapK`. # AppFunc -Applicative functors can be composed, can traverse traversable functors, and we can obtain the product. +AppFunc extends Func to wrap around a special type of functor: Applicative functors. + +Applicative functors can be `compose`d, can `traverse` traversable functors, and we can obtain a `product` between two AppFuncs. ## Composition -Compose and andThen +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 have a List of executions, and want to List things, and discart some (`Option`). + +To achieve this nested context behavior `AppFunc` uses the `Nested` datatype. + +```scala mdoc:silent:nest +val AppFuncF: AppFunc[Option,Int,Int] = Func.appFunc((i: Int) => if (i==0) None else Some(i)) +val AppFuncG: AppFunc[List,Int,Int] = Func.appFunc((o: Int) => {List(o+1)}) +(AppFuncF andThen AppFuncG).run(1) //Nested(Some(List(2))) + +(AppFuncF andThen AppFuncG).run(0) //Nested(None) + +// same thing with compose + +(AppFuncG compose AppFuncF) + +``` ## Product +Applicative functors, like monads, are closed under product. Cats models this data type as Tuple2k, allowing to form the product of two applicative functors (they can be different!) in one data type. + +For further reading: [hearding cats](http://eed3si9n.com/herding-cats/combining-applicative.html#Product+of+applicative+functions) + +```scala mdoc:silent:nest +(AppFuncF product AppFuncG).run(1) +``` ## Traverse +Finally, the main subject of the [The Essence of the Iterator Pattern](https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf) is that given the properties of the applictive functor, we can iterate over traversable types applying an applicative functor. + +```scala mdoc:silent:nest +AppFuncF.traverse(List(1,2,3)) +// val res14: Option[List[Int]] = Some(List(1, 2, 3)) + +AppFuncF.traverse(List(1,2,0)) +//val res15: Option[List[Int]] = None +``` + From 6036cdda8b5d51576b0c2c9a8ef405ffb5327272 Mon Sep 17 00:00:00 2001 From: Ulises Date: Thu, 19 Jan 2023 13:42:01 +0100 Subject: [PATCH 04/15] renaming and adding a link to AppFunc doc --- docs/datatypes/func.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index b4e543f1bf..736fb78905 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -12,14 +12,15 @@ 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 natTransformation = new (Option ~> List) { +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(natTransformation(_)) +g.map(optToList(_)) +// val res0: cats.data.Func[List,Int,List[String]] = ... ``` @@ -28,11 +29,11 @@ g.map(natTransformation(_)) AppFunc extends Func to wrap around a special type of functor: Applicative functors. -Applicative functors can be `compose`d, can `traverse` traversable functors, and we can obtain a `product` between two AppFuncs. +Applicative functors can be `compose`d, can `traverse` traversable functors, and we can obtain a `product` between two AppFuncs. ## 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 have a List of executions, and want to List things, and discart some (`Option`). +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 discart some (`Option`). To achieve this nested context behavior `AppFunc` uses the `Nested` datatype. @@ -60,7 +61,9 @@ For further reading: [hearding cats](http://eed3si9n.com/herding-cats/combining- ``` ## Traverse -Finally, the main subject of the [The Essence of the Iterator Pattern](https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf) is that given the properties of the applictive functor, we can iterate over traversable types applying an applicative functor. +Finally, the main subject of the [The Essence of the Iterator Pattern](https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf) is that given the properties of the applictive functor, we can iterate over traversable types applying an applicative functor. + +This explained in the implementation of the Applicative trait: [Applicative - Traverse](https://typelevel.org/cats/typeclasses/applicative.html#traverse) ```scala mdoc:silent:nest AppFuncF.traverse(List(1,2,3)) From 700099bc470a9c770fb47f7720890ba4345b691c Mon Sep 17 00:00:00 2001 From: Ulises Date: Thu, 19 Jan 2023 13:52:13 +0100 Subject: [PATCH 05/15] bit of rewording in AppFunc docs --- docs/datatypes/func.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index 736fb78905..891d177155 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -25,15 +25,15 @@ g.map(optToList(_)) -# AppFunc +# AppFunc AppFunc extends Func to wrap around a special type of functor: Applicative functors. -Applicative functors can be `compose`d, can `traverse` traversable functors, and we can obtain a `product` between two AppFuncs. +With applicative functors we can `compose`, form the `product`, and also `traverse` traversable functors ## 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 discart some (`Option`). +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 discart some (`Option`). To achieve this nested context behavior `AppFunc` uses the `Nested` datatype. @@ -44,11 +44,9 @@ val AppFuncG: AppFunc[List,Int,Int] = Func.appFunc((o: Int) => {List(o+1)}) (AppFuncF andThen AppFuncG).run(1) //Nested(Some(List(2))) (AppFuncF andThen AppFuncG).run(0) //Nested(None) - // same thing with compose (AppFuncG compose AppFuncF) - ``` ## Product From a029422b34827aee0a3aff74f7a96643123ec8ab Mon Sep 17 00:00:00 2001 From: Ulises Date: Fri, 20 Jan 2023 12:01:51 +0100 Subject: [PATCH 06/15] corrections to Func docs --- docs/datatypes/func.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index 891d177155..8705f9fdac 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -1,6 +1,10 @@ # Func -Func is a wrapper arround a `run` function `a => F[B]` where F is a functor. Given that, the Func data type is equiped with the known `map` function, and a `mapK` function to apply natural transformations (from a `F` Func get an `G` 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 `F` Func get an `G` Func). + +The signature: `Func[F[_], A, B]` refers to an `F` functor, `A` source type and `B` target type (`F[B]`). + +If you are familiar with `Kleisli` you can recognize it has a similar signature: `Kleisli[F[_], -A, B]` and `Func[F[_], A, B]`. Well yes, `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`. ## Quick example @@ -31,9 +35,12 @@ AppFunc extends Func to wrap around a special type of functor: Applicative funct 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`, we find an even more similar data type. `AppFunc` provides compositions of weaker constraint, allowing to compose `AppFunc[F[_], A, B]` 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 discart some (`Option`). +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. @@ -59,8 +66,6 @@ For further reading: [hearding cats](http://eed3si9n.com/herding-cats/combining- ``` ## Traverse -Finally, the main subject of the [The Essence of the Iterator Pattern](https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf) is that given the properties of the applictive functor, we can iterate over traversable types applying an applicative functor. - This explained in the implementation of the Applicative trait: [Applicative - Traverse](https://typelevel.org/cats/typeclasses/applicative.html#traverse) ```scala mdoc:silent:nest From 2cebdb65d8fbba771df0ffb2c0bbd2ab5269d4ef Mon Sep 17 00:00:00 2001 From: Ulises Torrella Date: Sat, 4 Mar 2023 18:57:55 +0100 Subject: [PATCH 07/15] Update docs/datatypes/func.md Co-authored-by: Justin Reardon --- docs/datatypes/func.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index 8705f9fdac..3c28eb78de 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -1,6 +1,6 @@ # 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 `F` Func get an `G` 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]` refers to an `F` functor, `A` source type and `B` target type (`F[B]`). From b8613ff9b20b9e1e75b8636d6f74ac0f779f7d80 Mon Sep 17 00:00:00 2001 From: Ulises Torrella Date: Sat, 4 Mar 2023 18:59:05 +0100 Subject: [PATCH 08/15] Update docs/datatypes/func.md Co-authored-by: Justin Reardon --- docs/datatypes/func.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index 3c28eb78de..e166873621 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -57,7 +57,7 @@ val AppFuncG: AppFunc[List,Int,Int] = Func.appFunc((o: Int) => {List(o+1)}) ``` ## Product -Applicative functors, like monads, are closed under product. Cats models this data type as Tuple2k, allowing to form the product of two applicative functors (they can be different!) in one data type. +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: [hearding cats](http://eed3si9n.com/herding-cats/combining-applicative.html#Product+of+applicative+functions) From 06def4910ab0285c5d7c1195b4305b55ac8668c8 Mon Sep 17 00:00:00 2001 From: Ulises Torrella Date: Sat, 4 Mar 2023 18:59:31 +0100 Subject: [PATCH 09/15] Update docs/datatypes/func.md Co-authored-by: Justin Reardon --- docs/datatypes/func.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index e166873621..01691f4ff8 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -59,7 +59,7 @@ val AppFuncG: AppFunc[List,Int,Int] = Func.appFunc((o: Int) => {List(o+1)}) 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: [hearding cats](http://eed3si9n.com/herding-cats/combining-applicative.html#Product+of+applicative+functions) +For further reading: [herding cats](http://eed3si9n.com/herding-cats/combining-applicative.html#Product+of+applicative+functions) ```scala mdoc:silent:nest (AppFuncF product AppFuncG).run(1) From e73fe8e49aab40bc0ea58b40bca06cd86cbf7ead Mon Sep 17 00:00:00 2001 From: Ulises Date: Sat, 4 Mar 2023 19:02:40 +0100 Subject: [PATCH 10/15] refactor in Func documentation capitalized vals --- docs/datatypes/func.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index 01691f4ff8..c567c8c145 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -45,15 +45,15 @@ All of functional programming revolves around composing, and functors cannot be To achieve this nested context behavior `AppFunc` uses the `Nested` datatype. ```scala mdoc:silent:nest -val AppFuncF: AppFunc[Option,Int,Int] = Func.appFunc((i: Int) => if (i==0) None else Some(i)) +val appFuncOption: AppFunc[Option,Int,Int] = Func.appFunc((i: Int) => if (i==0) None else Some(i)) -val AppFuncG: AppFunc[List,Int,Int] = Func.appFunc((o: Int) => {List(o+1)}) -(AppFuncF andThen AppFuncG).run(1) //Nested(Some(List(2))) +val appFuncList: AppFunc[List,Int,Int] = Func.appFunc((o: Int) => {List(o+1)}) +(appFuncOption andThen appFuncList).run(1) //Nested(Some(List(2))) -(AppFuncF andThen AppFuncG).run(0) //Nested(None) +(appFuncOption andThen appFuncList).run(0) //Nested(None) // same thing with compose -(AppFuncG compose AppFuncF) +(appFuncList compose appFuncOption) ``` ## Product @@ -62,17 +62,17 @@ Applicative functors, like monads, are closed under product. Cats models product For further reading: [herding cats](http://eed3si9n.com/herding-cats/combining-applicative.html#Product+of+applicative+functions) ```scala mdoc:silent:nest -(AppFuncF product AppFuncG).run(1) +(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 -AppFuncF.traverse(List(1,2,3)) +appFuncOption.traverse(List(1,2,3)) // val res14: Option[List[Int]] = Some(List(1, 2, 3)) -AppFuncF.traverse(List(1,2,0)) +appFuncOption.traverse(List(1,2,0)) //val res15: Option[List[Int]] = None ``` From 0ea4eff0219113637af77b6fe7740e22b3d30a76 Mon Sep 17 00:00:00 2001 From: Ulises Torrella Date: Sat, 4 Mar 2023 19:04:33 +0100 Subject: [PATCH 11/15] Update docs/datatypes/func.md Co-authored-by: Justin Reardon --- docs/datatypes/func.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index c567c8c145..4c598b371e 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -2,9 +2,7 @@ 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]` refers to an `F` functor, `A` source type and `B` target type (`F[B]`). - -If you are familiar with `Kleisli` you can recognize it has a similar signature: `Kleisli[F[_], -A, B]` and `Func[F[_], A, B]`. Well yes, `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`. +The signature `Func[F[_], A, B]` is very similar to the signature for [Kleisli]: `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`. ## Quick example From aee7268fed7eb9c45c6d55b9f9acbaf63e22a469 Mon Sep 17 00:00:00 2001 From: Ulises Torrella Date: Sat, 4 Mar 2023 19:05:37 +0100 Subject: [PATCH 12/15] Update docs/datatypes/func.md Co-authored-by: Justin Reardon --- docs/datatypes/func.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index 4c598b371e..5f350e1470 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -35,7 +35,7 @@ With applicative functors we can `compose`, form the `product`, and also `traver Signature: `AppFunc[F[_], A, B] extends Func[F, A, B]` -Now, for the reader familiar with `Kleisli`, we find an even more similar data type. `AppFunc` provides compositions of weaker constraint, allowing to compose `AppFunc[F[_], A, B]` with `AppFunc[G[_], C, A]`. +Now, for the reader familiar with `Kleisli`, 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`). From 5b0faa9dde1cb5d47f7d3cb130808d93a1264431 Mon Sep 17 00:00:00 2001 From: Ulises Date: Sat, 4 Mar 2023 19:09:57 +0100 Subject: [PATCH 13/15] Include AppFunc in the Func documentation --- docs/datatypes/func.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index 5f350e1470..fa68ad265a 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -1,4 +1,4 @@ -# Func +# Func and AppFunc 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]`). From 1af3c3bdbf44f417add8a58598089267fdf5f2a5 Mon Sep 17 00:00:00 2001 From: Ulises Date: Sat, 4 Mar 2023 19:42:00 +0100 Subject: [PATCH 14/15] include links in Func docs and added my name to contributors --- AUTHORS.md | 1 + docs/datatypes/func.md | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 34ea0d8230..0fee3adfc2 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -280,6 +280,7 @@ possible: * Travis Brown * Trond Bjerkestrand * Tya + * Ulises Torrella * Valentin Willscher * Valeriy Avanesov * Valy Diarrassouba diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index fa68ad265a..80409cd822 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -1,8 +1,10 @@ # 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]: `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`. +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`. ## Quick example @@ -29,13 +31,13 @@ g.map(optToList(_)) # AppFunc -AppFunc extends Func to wrap around a special type of functor: Applicative functors. +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`, 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]`. +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`). From c815fe08b2ae8cd98352d63f4a167c6eae6376f2 Mon Sep 17 00:00:00 2001 From: Ulises Date: Sun, 5 Mar 2023 11:13:16 +0100 Subject: [PATCH 15/15] link to nested --- docs/datatypes/func.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/datatypes/func.md b/docs/datatypes/func.md index 80409cd822..5daf8de44c 100644 --- a/docs/datatypes/func.md +++ b/docs/datatypes/func.md @@ -42,7 +42,7 @@ Now, for the reader familiar with [Kleisli](../datatypes/kleisli.md), we find an 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. +To achieve this nested context behavior `AppFunc` uses the [`Nested`] datatype. ```scala mdoc:silent:nest val appFuncOption: AppFunc[Option,Int,Int] = Func.appFunc((i: Int) => if (i==0) None else Some(i))