diff --git a/README.md b/README.md index 33be563f060..717c6385f10 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,11 @@ [![codecov](https://codecov.io/gh/arrow-kt/arrow/branch/master/graph/badge.svg)](https://codecov.io/gh/arrow-kt/arrow) Λrrow is a library for Typed Functional Programming in Kotlin. -It includes the most popular data types, type classes and abstractions such as `Option`, `Try`, `Either`, `IO`, `Functor`, `Applicative`, `Monad` and many more empowering users to define pure FP apps and libraries built atop higher order abstractions. Use the below list to learn more about Λrrow's main features. + +Arrow aims to provide a [*lingua franca*](https://en.wikipedia.org/wiki/Lingua_franca) of interfaces and abstractions across Kotlin libraries. +For this, it includes the most popular data types, type classes and abstractions such as `Option`, `Try`, `Either`, `IO`, `Functor`, `Applicative`, `Monad` to empower users to write pure FP apps and libraries built atop higher order abstractions. + +Use the list below to learn more about Λrrow's main features. - [Documentation](http://arrow-kt.io) - [Patterns](http://arrow-kt.io/docs/patterns/glossary/): tutorials and approaches to day-to-day challenges using FP diff --git a/modules/docs/arrow-docs/docs/_data/menu.yml b/modules/docs/arrow-docs/docs/_data/menu.yml index e9465da2801..69bb5378d99 100644 --- a/modules/docs/arrow-docs/docs/_data/menu.yml +++ b/modules/docs/arrow-docs/docs/_data/menu.yml @@ -28,6 +28,9 @@ options: - title: Dependency Injection url: /docs/patterns/dependency_injection/ + - title: The Monad Tutorial + url: /docs/patterns/monads/ + - title: Monad Comprehensions url: /docs/patterns/monad_comprehensions/ diff --git a/modules/docs/arrow-docs/docs/docs/README.md b/modules/docs/arrow-docs/docs/docs/README.md index 0f26870d32f..14572a0b9b4 100644 --- a/modules/docs/arrow-docs/docs/docs/README.md +++ b/modules/docs/arrow-docs/docs/docs/README.md @@ -12,7 +12,10 @@ NOTE: The docs are currently at around 60% completion. They're the present prior [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) Λrrow is a library for Typed Functional Programming in Kotlin. -It includes the most popular data types, type classes and abstractions such as `Option`, `Try`, `Either`, `IO`, `Functor`, `Applicative`, `Monad` to empower users to define pure FP apps and libraries built atop higher order abstractions. + +Arrow aims to provide a [*lingua franca*](https://en.wikipedia.org/wiki/Lingua_franca) of interfaces and abstractions across Kotlin libraries. +For this, it includes the most popular data types, type classes and abstractions such as `Option`, `Try`, `Either`, `IO`, `Functor`, `Applicative`, `Monad` to empower users to write pure FP apps and libraries built atop higher order abstractions. + Use the list below to learn more about Λrrow's main features. - [Patterns](http://arrow-kt.io/docs/patterns/glossary/): tutorials and approaches to day-to-day challenges using FP diff --git a/modules/docs/arrow-docs/docs/docs/patterns/glossary/README.md b/modules/docs/arrow-docs/docs/docs/patterns/glossary/README.md index 7156006f431..ec2e62893e6 100644 --- a/modules/docs/arrow-docs/docs/docs/patterns/glossary/README.md +++ b/modules/docs/arrow-docs/docs/docs/patterns/glossary/README.md @@ -28,24 +28,34 @@ You can read more about all the [datatypes]({{ '/docs/datatypes/intro' | relativ ### Typeclasses -Typeclasses define a set of functions associated to one type. -This behavior is checked by a test suite called the "laws" for that typeclass. +Typeclasses are interface abstractions that define a set of extension functions associated to one type. +These extension functions are canonical and consistent across languages and libraries; +and they have inherent mathematical properties that are testable, such as commutativity or associativity. -You can use typeclasses as a DSL to add new free functionality to an existing type -or treat them as an abstraction placeholder for any one type that can implement the typeclass. - -Examples of these behaviors are: comparability ([`Eq`]({{ '/docs/typeclasses/eq' | relative_url }})), +Examples of behaviours abstracted by typeclasses are: comparability ([`Eq`]({{ '/docs/typeclasses/eq' | relative_url }})), composability ([`Monoid`]({{ '/docs/typeclasses/monoid' | relative_url }})), its contents can be mapped from one type to another ([`Functor`]({{ '/docs/typeclasses/functor' | relative_url }})), or error recovery ([`MonadError`]({{ '/docs/typeclasses/monaderror' | relative_url }})). +Typeclasses have two main uses: + +* Add new functionality to types. For example, if I know how to compare two objects I can add a new extension function to check for inequality. +Or if I know how to aggregate objects together, I can add an extension function for `List` that aggregates all of its elements. +The number of extra extra extension functions that you get per typeclass can be from one in `Eq` to 17 (!) in `Foldable`. + +* Abstracting over behavior. Like any other interface, you can use them in your functions and classes as a way of talking about the capabilities of the implementation, +without exposing the details. This way you can create APIs that work the same for `Option`, `Try`, or `Observable`. + You can read more about all the [typeclasses]({{ '/docs/typeclasses/intro' | relative_url }}) that Arrow provides in its [section of the docs]({{ '/docs/typeclasses/intro' | relative_url }}). -One example, the typeclass `Eq` parametrized to `F` defines equality between two objects of type `F`: +Let's dive in one example. The typeclass `Eq` parametrized to `F` defines equality between two objects of type `F`: ```kotlin interface Eq { fun F.eqv(b: F): Boolean + + fun F.neqv(b: F): Boolean = + !eqv(b) } ``` diff --git a/modules/docs/arrow-docs/docs/docs/patterns/monadcomprehensions/README.md b/modules/docs/arrow-docs/docs/docs/patterns/monadcomprehensions/README.md index d6da96458b4..d96318bf43a 100644 --- a/modules/docs/arrow-docs/docs/docs/patterns/monadcomprehensions/README.md +++ b/modules/docs/arrow-docs/docs/docs/patterns/monadcomprehensions/README.md @@ -9,8 +9,12 @@ permalink: /docs/patterns/monad_comprehensions/ {:.intermediate} intermediate -Monad comprehension is the name for a programming idiom available in multiple languages. +Monad comprehensions is the name for a programming idiom available in multiple languages like JavaScript, F#, Scala, or Haskell. The purpose of monad comprehensions is to compose sequential chains of actions in a style that feels natural for programmers of all backgrounds. +They're similar to coroutines or async/await, but extensible to existing and new types! + +Let's walk through the evolution of how code was written, up to where comprehensions are today. +It'll take a couple of sections to get there, so if you're familiar with `flatMap` feel free to skip to [Comprehensions over coroutines]({{ '/docs/patterns/monad_comprehensions/#comprehensions-over-coroutines' | relative_url }}). ### Synchronous sequences of actions @@ -37,12 +41,15 @@ They allow us to write sequenced code that can be run asynchronously over multip ### Asynchronous sequences of actions -The general representation of sequenced execution in code is called a [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}). This typeclass is a short API for sequencing code, summarised in a single function `flatMap`. -It takes as a parameter one function to be called after the current operation completes, and that function has to return another [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}) to continue the operation with. -A common renaming of `flatMap` is `andThen`. Go to the documentation page to see a deep dive on the Monad API. - +The abstraction of sequencing execution of code is summarised in a single function that in Arrow is called `flatMap`, +although you may find it in other languages referred as `andThen`, `then`, `bind`, or `SelectMany`. +It takes as a parameter one function to be called after the current operation completes, and that function has to return another value to continue the operation with. With knowledge of `flatMap` we can write sequential expressions that are ran asynchronously, even over multiple threads. -Implementations of `Monad` are available for internal types like `Try` and also integrations like [RxJava 2]({{ '/docs/integrations/rx2' | relative_url }}) and [kotlinx.coroutines]({{ '/docs/integrations/kotlinxcoroutines' | relative_url }}). + +The [typeclass]({{ '/docs/typeclasses/intro' | relative_url }}) interface that abstracts sequenced execution of code via `flatMap` is called a [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}), +for which we also have a [tutorial]({{ '/docs/patterns/monads' | relative_url }}). + +Implementations of [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}) are available for internal types like `Try` and also integrations like [RxJava 2]({{ '/docs/integrations/rx2' | relative_url }}) and [kotlinx.coroutines]({{ '/docs/integrations/kotlinxcoroutines' | relative_url }}). Let's see one example using a [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}) called [`IO`]({{ '/docs/effects/io' | relative_url }}), where we fetch from a database the information about the dean of university some student attends: ```kotlin @@ -67,6 +74,9 @@ This feature is known with multiple names: async/await, coroutines, do notation, In Kotlin, coroutines (introduced in version 1.1 of the language) make the compiler capable of rewriting seemingly synchronous code intro asynchronous sequences. Arrow uses this capability of the compiler to bring you coroutines-like notation to all instances of the [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}) typeclass. +This means that comprehensions are available for `Option`, `Try`, `List`, `Reader`, `Observable`, `Flux` or `IO` all the same. +In the following examples we'll use `IO` as it's a simple concurrency primitive with straightforward behavior. + Every instance of [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}) contains a method `binding` that receives a suspended function as a parameter. This functions must return the last element of the sequence of operations. Let's see a minimal example. diff --git a/modules/docs/arrow-docs/docs/docs/patterns/monads/README.md b/modules/docs/arrow-docs/docs/docs/patterns/monads/README.md new file mode 100644 index 00000000000..27fa250c0c5 --- /dev/null +++ b/modules/docs/arrow-docs/docs/docs/patterns/monads/README.md @@ -0,0 +1,686 @@ +--- +layout: docs +title: The Monad Tutorial +permalink: /docs/patterns/monads/ +--- + +## Monads explained in Kotlin (again) + +{:.intermediate} +intermediate + +### Credits + +This doc has been adapted from Mikhail Shilkov's blog entry [`Monads explained in C# (again)`](https://mikhail.io/2018/07/monads-explained-in-csharp-again/). It attempts to explain the rationale behind Monads, providing simple examples and how they relate to standard library constructs. + +If you're just interested in the API, head down to the [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}) typeclass page. + +### Intro + +I love functional programming for the simplicity that it brings. + +But at the same time, I realize that learning functional programming is a challenging process. FP comes with a baggage of unfamiliar vocabulary that can be daunting for somebody coming from an object-oriented language like Kotlin. + +![](https://mikhail.io/2018/07/monads-explained-in-csharp-again//functional-programming-word-cloud.png) + +*some of functional lingo* + +`Monad` is probably the most infamous term from the list above. Monads have reputation of being something very abstract and very confusing. + +### The Fallacy of Monad Tutorials + +Numerous attempts were made to explain monads in simple definitions; and monad tutorials have become a genre of its own. And yet, times and times again, they fail to enlighten the readers. + +The shortest explanation of monads looks like this: + +> A Monad is just a monoid in the category of endofunctors + +It's both mathematically correct and totally useless to anybody learning functional programming. To understand this statement, one has to know the terms "monoid", "category" and "endofunctors" and be able to mentally compose them into something meaningful. + +The same problem is apparent in most monad tutorials. They assume some pre-existing knowledge in heads of their readers, and if that assumption fails, the tutorial doesn't click. + +Focusing too much on mechanics of monads instead of explaining why they are important is another common problem. + +Douglas Crockford grasped this fallacy very well: + +>The monadic curse is that once someone learns what monads are and how to use them, they lose the ability to explain them to other people + +The problem here is likely the following. Every person who understands monads had their own path to this knowledge. It hasn't come all at once, instead there was a series of steps, each giving an insight, until the last final step made the puzzle complete. + +But they don't remember the whole path anymore. They go online and blog about that very last step as the key to understanding, joining the club of flawed explanations. + +There is an actual academic paper from Tomas Petricek that studies monad tutorials. + +I've read that paper and a dozen of monad tutorials online. And of course, now I came up with my own. + +I'm probably doomed to fail too, at least for some readers. + +### Story of Composition + +The base element of each functional program is Function. In typed languages each function is just a mapping between the type of its input parameter and output parameter. Such type can be annotated as `func: TypeA -> TypeB`. + +Kotlin is an object-oriented language, so we use methods to declare functions. There are two ways to define a method comparable to function `func` above. I can use a top level "static" method: + +```kotlin +fun func(a: ClassA): ClassB { ... } +``` + +... or an instance method: + +```kotlin +class ClassA { + // Instance method + fun func(): ClassB { ... } +} +``` + +The top level form looks closer to the function notation, but both ways are actually equivalent for the purpose of our discussion. I will use instance methods in my examples, however all of them could be written as top level extension methods too. + +How do we compose more complex workflows, programs and applications, out of such simple building blocks? A lot of patterns in both OOP and FP worlds revolve around this question. And monads are one of the answers. + +My sample code is going to be about conferences and speakers. The method implementations aren't really important, just watch the types carefully. There are 4 classes (types) and 3 methods (functions): + +```kotlin +class Speaker { + fun nextTalk(): Talk { ... } +} + +class Talk { + fun getConference(): Conference { ... } +} + +class Conference { + fun getCity(): City { ... } +} + +class City { ... } +``` + +These methods are currently very easy to compose into a workflow: + +```kotlin +fun nextTalkCity(speaker: Speaker): City { + val talk = speaker.nextTalk() + val conf = talk.getConference() + val city = conf.getCity() + return city +} +``` + +Because the return type of the previous step always matches the input type of the next step, we can write it even shorter: + +```kotlin +fun nextTalkCity(speaker: Speaker): City = + speaker + .nextTalk() + .getConference() + .getCity() +``` + +This code looks quite readable. It's concise and it flows from top to bottom, from left to right, similar to how we are used to read any text. There is not much noise too. + +That's not what real codebases look like though, because there are multiple complications along the happy composition path. Let's look at some of them. + +### NULLs + +Any class instance in Kotlin can be null. In the example above I might get runtime errors if one of the methods ever returns null back. + +Typed functional programming always tries to be explicit about types, so I'll re-write the signatures of my methods to annotate the return types as nullables: + +```kotlin +class Speaker { + fun nextTalk(): Talk? { ... } +} + +class Talk { + fun getConference(): Conference? { ... } +} + +class Conference { + fun getCity(): City? { ... } +} + +class City { ... } +``` + +Now, when composing our workflow, we need to take care of null results: + +```kotlin +fun nextTalkCity(speaker: Speaker?): City? { + if (speaker == null) return null + + val talk = speaker.nextTalk() + if (talk == null) return null + + val conf = talk.getConference() + if (conf == null) return null + + val city = conf.getCity() + return city +} +``` + +It's still the same method, but it got more noise now. Even though I used short-circuit returns and one-liners, it still got harder to read. + +To fight that problem, smart language designers came up with the [Safe Call Operator](https://kotlinlang.org/docs/reference/null-safety.html#safe-calls): + +```kotlin +fun nextTalkCity(speaker: Speaker?): City? { + return + speaker + ?.nextTalk() + ?.getConference() + ?.getCity() +} +``` + +Now we are almost back to our original workflow code: it's clean and concise, we just got 3 extra `?` symbols around. + +Let's take another leap. + +### Collections + +Quite often a function returns a collection of items, not just a single item. To some extent, that's a generalization of `null` case: with `T?` we might get 0 or 1 results back, while with a collection we can get 0 to any n results. + +Our sample API could look like this: + +```kotlin +class Speaker { + fun getTalks(): List { ... } +} + +class Talk { + fun getConferences(): List { ... } +} + +class Conference { + fun getCities() List { ... } +} +``` + +I used `List` but it could be any class or even a `Sequence`. + +How would we combine the methods into one workflow? The traditional version would look like this: + +```kotlin +fun allCitiesToVisit(speaker: Speaker): List { + val result = mutableListOf() + + for (talk in speaker.getTalks()) + for (conf in talk.getConferences()) + for (city in conf.getCities()) + result.add(city) + + return result +} +``` + +It reads ok-ish still. But the combination of nested loops and mutation with some conditionals sprinkled on them can get unreadable pretty soon. The exact workflow might be lost in the mechanics. + +As an alternative, the Kotlin language designers included extension methods. We can write code like this: + +```kotlin +fun allCitiesToVisit(speaker: Speaker): List { + return + speaker + .getTalks() + .flatMap { talk -> talk.getConferences() } + .flatMap { conf -> conf.getCities() } +} +``` + +Let me do one further trick and format the same code in an unusual way: + +```kotlin +fun allCitiesToVisit(Speaker speaker): List { + return + speaker + .getTalks() .flatMap { x -> x + .getConferences() }.flatMap { x -> x + .getCities() } +} +``` + +Now you can see the original original code on the left, combined with just a bit of technical repetitive clutter on the right. Hold on, I'll show you where I'm going. + +Let's discuss another possible complication. + +### Asynchronous Calls + +What if our methods need to access some remote database or service to produce the results? This should be shown in type signature, +and we can imagine a library providing a `Task` type for that: + +```kotlin +class Speaker { + fun nextTalk(): Task { ... } +} + +class Talk { + fun getConference(): Task { ... } +} + +class Conference { + fun getCity(): Task { ... } +} +``` + +This change breaks our nice workflow composition again. + +```kotlin +fun nextTalkCity(speaker: Speaker): Task { + return + speaker.nextTalk().execute() + .then { talk -> talk.getConference() }.execute() + .then { conf -> conf.getCity() }.execute() +} +``` + +Hard to read, but let me apply my formatting trick again: + +```kotlin +fun nextTalkCity(speaker: Speaker): Task { + return + speaker + .nextTalk() .execute().then { x -> x + .getConference() }.execute().then { x -> x + .getCity() } +} +``` + +You can see that, once again, it's our nice readable workflow on the left plus some mechanical repeatable junction code on the right. + +### Pattern + +Can you see a pattern yet? + +I'll repeat the `T?`, `List` `Task`-based workflows again: + +```kotlin +fun nextTalkCity(speaker: Speaker?): City? { + return + speaker ? + .nextTalk() ? + .getConference() ? + .getCity() +} + +fun allCitiesToVisit(speaker: Speaker): List { + return + speaker + .getTalks() .flatMap { x -> x + .getConferences() }.flatMap { x -> x + .getCities() } +} + +fun nextTalkCity(speaker: Speaker): Task { + return + speaker + .nextTalk() .execute().then { x -> x + .getConference() }.execute().then { x -> x + .getCity() } +} +``` + +In all 3 cases there was a complication which prevented us from sequencing method calls fluently. In all 3 cases we found the gluing code to get back to fluent composition. + +Let's try to generalize this approach. Given some generic container type `WorkflowThatReturns`, we have a method to combine an instance of such workflow with a function which accepts the result of that workflow and returns another workflow back: + +```kotlin +class WorkflowThatReturns { + fun addStep(step: (T) -> WorkflowThatReturns): WorkflowThatReturns +} +``` + +In case this is hard to grasp, have a look at the picture of what is going on: + +![](https://mikhail.io/2018/07/monads-explained-in-csharp-again//monad-bind.png) + +An instance of type `T` sits in a generic container. + +We call `addStep` with a function, which maps `T` to `U` sitting inside yet another container. + +We get an instance of `U` but inside two containers. + +Two containers are automatically unwrapped into a single container to get back to the original shape. + +Now we are ready to add another step! + +In the following code, `nextTalk` returns the first instance inside the container: + +```kotlin +fun workflow(speaker: Speaker): WorkflowThatReturns { + return + speaker + .nextTalk() + .addStep { x -> x.getConference() } + .addStep { x -> x.getCity() } +} +``` + +Subsequently, `addStep` is called two times to transfer to `Conference` and then `City` inside the same container: + +![](https://mikhail.io/2018/07/monads-explained-in-csharp-again//monad-two-binds.png) + +### Finally, Monads + +The name of this pattern is **Monad**. + +In Arrow terms, a Monad is an interface with two operations: a constructor `just`, and `flatMap`. + +```kotlin +interface Monad: Applicative, Functor { + fun just (instance: A): Kind + + fun Kind.flatMap(f: (A) -> Kind) +} +``` + +The constructor `just` is used to put an object into container `Kind` as described in the [glossary]({{ '/docs/patterns/glossary/#type-constructors' | relative_url }}), `flatMap` is used to replace one contained object with another contained object. + +It's important that `flatMaps`'s argument returns `Kind` and not just `B`, as this new contained object can have different behavior, like a left branch of `Either` or an async execution of `IO`. + +We can think of `flatMap` as a combination of `map` and `flatten as defined by the following signature: + +```kotlin +fun Kind.map(f: (A) -> B): Kind // Inherited from Functor + +fun Kind>.flatten(): Kind + +container + .map { x -> just(x + 1) } // Kind> + .flatten() // Kind +``` + +Even though I spent quite some time with examples, I expect you to be slightly confused at this point. That's ok. + +Keep going and let's have a look at several sample implementations of Monad pattern. + +### Option + +My first example was with nullable `?`. The full pattern containing either 0 or 1 instance of some type is called Option (it maybe has a value, or maybe not). + +Option is another approach to dealing with "no value" value, alternative to the concept of null. You can read more about [`Option`]({{ '/docs/datatypes/option' | relative_url }}) to see how Arrow implemented it. + +When null is not allowed, any API contract gets more explicit: either you return type `T` and it's always going to be filled, or you return `Option`. +The client will see that Option type is used, so it will be forced to handle the case of absent value. + +Given an imaginary repository contract (which does something with customers and orders): + +```kotlin +interface OptionAwareRepository { + fun getCustomer(id: Int): Option + + fun getAddress(id: Int): Option
+ + fun getOrder(id: Int): Option +} +``` + +The client can be written with `flatMap` method composition, without branching, in fluent style: + +```kotlin +fun shipperOfLastOrderOnCurrentAddress(customerId: Int): Option = + repo.getCustomer(customerId) + .flatMap { c -> c.address } + .flatMap { a -> repo.getAddress(a.id) } + .flatMap { a -> a.lastOrder } + .flatMap { lo -> repo.getOrder(lo.id) } + .flatMap { o -> o.shipper } +``` + +### Sequence can implement Monad + +Sequence is an interface for enumerable containers. + +You might have used `Sequence` before, but what you probably didn't know is that it's a container type that fills the requirements of a Monad: sequences can be constructed, and they can be flatMapped. + +Here's the signature of `flatMap` as defined in the [Kotlin standard library](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/flat-map.html): + +```kotlin +fun Sequence.flatMap( + transform: (T) -> Sequence +): Sequence +``` + +And here is an example of composition: + +```kotlin +val shippers: Sequence = + customers + .flatMap { c -> c.addresses } + .flatMap { a -> a.orders } + .flatMap { o -> o.shippers } +``` + +The query has no idea about how the collections are stored (encapsulated in containers). We use functions of type `A -> Sequence` to produce new sequences using the `flatMap` operation. + +### Deferred (Future) + +In the Kotlin coroutines library `Deferred` type is used to denote asynchronous computation which will eventually return an instance of `T`. +The other names for similar concepts in other languages are Promise and Future. + +While the typical usage of Deferred in Kotlin is different from the Monad pattern we discussed, I can still come up with a Future class with the familiar structure: + +```kotlin +class Future( + val instance: Deferred +) { + constructor(instance: T) { + this.instance = async(LAZY) { instance } + } + + constructor(instance: Deferred) { + this.instance = instance + } + + fun flatMap(func: (T) -> Future): Future = + Future(async(LAZY) { + val t = instance.await() + func(t).await() + }) + + fun runSync(action: (Try) -> Unit) = + runBlocking { action(Try { instance.await() }) } +} +``` + +Effectively, it's just a wrapper around the Deferred which doesn't add too much value, but it's a useful illustration because now we can do: + +```kotlin +repository + .loadSpeaker() + .flatMap { speaker -> speaker.nextTalk() + .flatMap { talk -> talk.getConference() } + .flatMap { conference -> conference.getCity().map { it to speaker } } + } + .runSync { (city, speaker) -> city.fold( + { Logger.logError(it); reservations.cancel() }, + { reservations.bookFlight(speaker, city) }) + } +``` + +We are back to the familiar structure. Time for some more complications. + +### Abstraction for all Monads + +We're going to dispel one common misconception. +Sometimes the word Monad is used to refer to types like Option, Future, Either... and so on, and that's not correct. +Those are called [datatypes]({{ '/docs/datatypes/intro' | relative_url }}) or just types. Let's see the difference! + +As you have seen, neither Future nor Option implement Monad directly. +This is intentional, as you can potentially have several Monad implementations for a single type. +For example, RxJava's Observable can be chained using `flatMap`, `switchMap`, and `concatMap`, and using each is still a Monad. + +Instead, Arrow specifies that Monad must be implemented by a separate object or class, referred as the "instance of Monad for type F". + +```kotlin +object FutureMonadInstance: Monad { + override fun just (instance: A): Future = + Future(a) + + override fun FutureOf.flatMap(f: (A) -> FutureOf): Future = + flatMap(f) // as per precedence rules the class method is called +} + +object OptionMonadInstance: Monad { + override fun just (instance: A): Option = + Some(a) + + override fun OptionOf.flatMap(f: (A) -> OptionOf): Option = + flatMap(f) // as per precedence rules the class method is called +} + +object ObservableSwitchMonadInstance: Monad { + override fun just (instance: A): Observable = + Observable.just(a) + + override fun ObservableOf.flatMap(f: (A) -> ObservableOf): Observable = + switchMap(f) +} + +object ObservableConcatMonadInstance: Monad { + override fun just (instance: A): Observable = + Observable.just(a) + + override fun ObservableOf.flatMap(f: (A) -> ObservableOf): Observable = + concatMap(f) +} +``` + +What are the benefits of separating the instances from the direct implementation, causing a duplication in methods and an extra layer of indirection? + +The main use case is allowing you to write code that is generic for any object that can provide a Monad instance object. + +```kotlin +fun Monad.shipperOfLastOrderOnCurrentAddress(customerId: Int): Kind = + repo.getCustomer(customerId) + .flatMap { c -> c.address } + .flatMap { a -> repo.getAddress(a.id) } + .flatMap { a -> a.lastOrder } + .flatMap { lo -> repo.getOrder(lo.id) } + .flatMap { o -> o.shipper } +``` +In this case, like with any other interface, Monad defines the API and behavior but not the implementation details. +This pattern is specially useful for libraries that must remain agnostic to implementations, and gives them a *lingua franca* when building on top of each other. + +Using the Monad and other similar abstractions, Arrow can provide a rich collection of extension functions and new language extensions that can be reused by other codebases. + +You can read more about generalizing code in the [glossary]({{ '/docs/patterns/glossary' | relative_url }}) and [typeclasses intro]({{ '/docs/typeclasses/intro' | relative_url }}). + +### Non-Sequential Workflows + +Up until now, all the composed workflows had very linear, sequential structure: the output of a previous step was always the input for the next step. +That piece of data could be discarded after the first use because it was never needed for later steps: + +![](https://mikhail.io/2018/07/monads-explained-in-csharp-again//linear-workflow.png) + +Quite often though, this might not be the case. A workflow step might need data from two or more previous steps combined. + +In the example above, `bookFlight` method might actually needs both `Speaker` and `City` objects: + +![](https://mikhail.io/2018/07/monads-explained-in-csharp-again//non-linear-workflow.png) + +In this case, we would have to use a lambda to save the speaker variable until we get a talk too: + +```kotlin +repository + .loadSpeaker() + .runSync { speaker -> + speaker + .nextTalk() + .flatMap { talk -> talk.getConference() } + .flatMap { conference -> conference.getCity() } + .runSync { city -> city.fold( + { Logger.logError(it); reservations.cancel() }, + { reservations.bookFlight(speaker, city) }) + } + ) + } +``` + +Obviously, this gets ugly very soon. + +To solve this structural problem the Kotlin language introduced coroutines, the design of which was inspired by other languages such as C# and JavaScript. + +If we move back to using Deferred instead of our custom Future, we are able to write + +```kotlin +val speaker = repository.loadSpeaker().await() +val talk = speaker.nextTalk().await() +val conference = talk.getConference().await() +val city = conference.getCity().await() +reservations.bookFlight(speaker, city).await() +``` + +Even though we lost the fluent syntax, at least the block has just one level, which makes it easier to read and navigate. + +By using coroutines Arrow provides a specialization that enables readable async/await style code for any Monad. +This specialization can be accessed using the function `binding` on any Monad, and the method `bind`. Internally, `Monad#flatMap` is used for chaining. + +```kotlin +fun bookSpeakersFlights(M: Monad): Kind = + M.binding { + val speaker = repository.loadSpeaker().bind() + val talk = speaker.nextTalk().bind() + val conference = talk.getConference().bind() + val city = conference.getCity().bind() + reservations.bookFlight(speaker, city).bind() + } + +bookSpeakersFlights(ObservableSwitchMonadInstance).fix() // Observable + +bookSpeakersFlights(OptionMonadInstance).fix() // Option + +bookSpeakersFlights(ListMonadInstance).fix() // List +``` + +These are called [Monad Comprehensions]({{ '/docs/patterns/monad_comprehensions' | relative_url }}), and you can find a complete section of the docs explaining it. + +### Monad Laws + +There are a couple laws that `just` constructor and `flatMap` need to adhere to, so that they produce a Monad with a stable implementation. +These laws are encoded in Arrow as tests you can find in the `arrow-test` module, and are already tested for all instances in the library. + +A typical monad tutorial will make a lot of emphasis on the laws, but I find them less important to explain to a beginner. Nonetheless, here they are for the sake of completeness. + +`Left Identity law` says that that Monad constructor is a neutral operation: you can safely run it before Bind, and it won't change the result of the function call: + +``` +// Given +val value: A +val f: (A) -> Kind + +// Then (== means both parts are equivalent) +just(value).flatMap(f) == f(value) +``` + +`Right Identity law` says that given a monadic value, wrapping its contained data into another monad of same type and then Binding it, doesn't change the original value: + +``` +// Given +val monadicValue: Kind + +// Then (== means both parts are equivalent) +monadicValue.flatMap { x -> just(x) } == monadicValue +``` + +`Associativity law` means that the order in which Bind operations are composed does not matter: + +``` +// Given +val m: Kind +val f: (T) -> Kind +val g: (T) -> Kind + +// Then (== means both parts are equivalent) +m.flatMap(f).flatMap(g) == m.flatMap { a -> f(a).flatMap(g) } +``` + +The laws may look complicated, but in fact they are very natural expectations that any developer has when working with monads, so don't spend too much mental effort on memorizing them. + +### Conclusion + +You should not be afraid of the "M-word" just because you are a Kotlin programmer. + +Kotlin does not have a notion of monads as predefined language constructs, but that doesn't mean we can't borrow some ideas from the functional world. +Having said that, it's also true that Kotlin is lacking some powerful ways to combine and generalize monads that are available in functional programming languages. diff --git a/modules/docs/arrow-docs/docs/docs/typeclasses/applicative/README.md b/modules/docs/arrow-docs/docs/docs/typeclasses/applicative/README.md index a5755ec6e84..a0b7280581d 100644 --- a/modules/docs/arrow-docs/docs/docs/typeclasses/applicative/README.md +++ b/modules/docs/arrow-docs/docs/docs/typeclasses/applicative/README.md @@ -125,6 +125,12 @@ The following datatypes in Arrow provide instances that adhere to the `Applicati - [NonEmptyList]({{ '/docs/datatypes/nonemptylist' | relative_url }}) - [Id]({{ '/docs/datatypes/id' | relative_url }}) - [Function0]({{ '/docs/datatypes/function0' | relative_url }}) +- [Observable]({{ '/docs/integrations/rx2' | relative_url }}) +- [Flowable]({{ '/docs/integrations/rx2' | relative_url }}) +- [Deferred]({{ '/docs/integrations/kotlinxcoroutines' | relative_url }}) +- [Flux]({{ '/docs/integrations/reactor' | relative_url }}) +- [Mono]({{ '/docs/integrations/reactor' | relative_url }}) +- [IO]({{ '/docs/effects/io' | relative_url }}) Additionally all instances of [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}) and their MTL variants implement the `Applicative` typeclass directly since they are all subtypes of `Applicative`. diff --git a/modules/docs/arrow-docs/docs/docs/typeclasses/intro/README.md b/modules/docs/arrow-docs/docs/docs/typeclasses/intro/README.md index bdcfaf38b4a..47232af085d 100644 --- a/modules/docs/arrow-docs/docs/docs/typeclasses/intro/README.md +++ b/modules/docs/arrow-docs/docs/docs/typeclasses/intro/README.md @@ -10,31 +10,33 @@ video: 3y9KI7XWXSY {:.beginner} beginner -Typeclasses define a set of functions associated to one generic type. -All methods inside a typeclass will have one of two shapes: - -* Constructor: create a new `Kind` from a value, a function, an error... Some examples are `just`, `raise`, `async`, `defer`, or `binding`. - -* Extensions: add new functionality to a value `A` or a container `Kind`, provided by an extension function. For example, `map`, `eqv`, `show`, `traverse`, `sequence`, or `combineAll`. +Typeclasses are interfaces that define a set of extension functions associated to one type. You may see them referred as "extension interfaces". -You can use typeclasses as a DSL to access new free functionality for an existing type, -or treat them as an abstraction placeholder for any one type that can implement the typeclass. -The extension functions are scoped within the typeclass so they do not pollute the global namespace! +The other purpose of these interfaces, like with any other unit of abstraction, +is to have a single shared definition of a common API and behavior shared across many types in different libraries and codebases. -What differentiates typeclasses from regular OOP inheritance is that typeclasses are meant to be implemented *outside* of their types. -The association is done using generic parametrization rather than subclassing by implementing the interface. This has multiple benefits: +What differentiates FP from OOP is that these interfaces are meant to be implemented *outside* of their types, instead of *by* the types. +Now, the association is done using generic parametrization rather than subclassing by implementing the interface. This has multiple benefits: -* You can treat typeclass implementations as stateless parameters because they're just a collection of functions * Typeclasses can be implemented for any class, even those not in the current project -* You can make available any one implementation of a typeclasses at any scope for the generic type they're associated with by using functions like `run` and `with` +* You can treat typeclass implementations as stateless parameters because they're just a collection of functions +* You can make the extensions provided by a typeclass for the type they're associated with by using functions like `run` and `with`. -To assure that a typeclass has been correctly implemented for a type, Arrow provides a test suite called the "laws" per typeclass. -These test suites are available in the module `arrow-tests`. +You can read all about how Arrow implements typeclasses in the [glossary]({{ '/docs/patterns/glossary/' | relative_url }}). +If you'd like to use typeclasses effectively in your client code you can head to the docs entry about [dependency injection]({{ '/docs/patterns/dependency_injection' | relative_url }}). #### Example -You can read all about how Arrow implements typeclasses in the [glossary]({{ '/docs/patterns/glossary/' | relative_url }}). -If you'd like to use typeclasses effectively in your client code you can head to the docs entry about [dependency injection]({{ '/docs/patterns/dependency_injection' | relative_url }}). +Let's define a typeclass for the behavior of equality between two objects, and we'll call it `Eq`: + +```kotlin +interface Eq { + fun T.eqv(b: T) + + fun T.neqv(b: T) = + !eqv(b) +} +``` For this short example we will make available the scope of the typeclass `Eq` implemented for the type `String`, by using `run`. This will make all the `Eq` extension functions, such as `eqv` and `neqv`, available inside the `run` block. @@ -54,6 +56,39 @@ stringEq.run { } ``` +and even use it as parametrization in a function call + +```kotlin + +fun List.filter(other: F, EQ: Eq) = + this.filter { EQ.run { it.eqv(other) } } + +listOf("1", "2", "3").filter("2", String.eq()) +// [2] + +listOf(1, 2, 3).filter(3, Eq { one, other -> one < other }) +// [1, 2] +``` + +#### Structure + +This section uses concepts explained in the [glossary]({{ '/docs/patterns/glossary/#type-constructors' | relative_url }}) like `Kind`, +make sure to check them beforehand or else jump to the next section. + +A few typeclasses can be defined for values, like `Eq` above, and the rest are defined for type constructors defined by `Kind` using a `For-` marker. +All methods inside a typeclass will have one of two shapes: + +* Constructor: create a new `Kind` from a value, a function, an error... Some examples are `just`, `raise`, `async`, `defer`, or `binding`. + +* Extensions: add new functionality to a value `A` or a container `Kind`, provided by an extension function. For example, `map`, `eqv`, `show`, `traverse`, `sequence`, or `combineAll`. + +You can use typeclasses as a DSL to access new extension functions for an existing type, +or treat them as an abstraction placeholder for any one type that can implement the typeclass. +The extension functions are scoped within the typeclass so they do not pollute the global namespace! + +To assure that a typeclass has been correctly implemented for a type, Arrow provides a test suite called the "laws" per typeclass. +These test suites are available in the module `arrow-tests`. + ### Typeclasses provided by Arrow We will list all the typeclasses provided in Arrow grouped by the module they belong to, and a short description of the behavior they abstract. diff --git a/modules/docs/arrow-docs/docs/docs/typeclasses/monad/README.md b/modules/docs/arrow-docs/docs/docs/typeclasses/monad/README.md index cba62c740d6..9d10fa992e0 100644 --- a/modules/docs/arrow-docs/docs/docs/typeclasses/monad/README.md +++ b/modules/docs/arrow-docs/docs/docs/typeclasses/monad/README.md @@ -9,8 +9,109 @@ permalink: /docs/typeclasses/monad/ {:.intermediate} intermediate -TODO. Meanwhile you can find a short description in the [intro to typeclasses]({{ '/docs/typeclasses/intro/' | relative_url }}). +`Monad` is a typeclass that abstracts over sequential execution of code. +This doc focuses on the methods provided by the typeclass. +If you'd like a long explanation of its origins with simple examples with nullable, `Option` and `List`, +head to [The Monad Tutorial]({{ '/docs/patterns/monads' | relative_url }}). +### Main Combinators + +`Applicative` includes all combinators present in [`Applicative`]({{ '/docs/typeclasses/applicative/' | relative_url }}). + +#### Kind#flatMap + +Takes a continuation function from the value `A` to a new `Kind`, and returns a `Kind`. +Internally, `flatMap` unwraps the value inside the `Kind` and applies the function to obtain the new `Kind`. + +Because `Kind` cannot be created until `A` is unwrapped, it means that one cannot exists until the other has been executed, effectively making them a sequential chain of execution. + +```kotlin:ank +import arrow.core.* +import arrow.instances.* + +Some(1).flatMap { a -> + Some(a + 1) +} +``` + +The improvement of `flatMap` over regular function composition is that `flatMap` understands about sealed datatypes, and allows for short-circuiting execution. + +```kotlin:ank +None.flatMap { a: Int -> + Some(a + 1) +} +``` + +```kotlin:ank +Right(1).flatMap { _ -> + Left("Error") +}.flatMap { b: Int -> + Right(b + 1) +} +``` + +Note that depending on the implementation of `Kind`, this chaining function may be executed immediately, i.e. for `Option` or `Either`; +or lazily, i.e. `IO` or `ObservableK`. + +#### Kind>#flatten + +Combines two nested elements into one `Kind` + +```kotlin:ank +ForOption extensions { + Some(Some(1)).flatten() +} +``` + +```kotlin:ank +ForOption extensions { + Some(None).flatten() +} +``` + +#### mproduct + +Like `flatMap`, but it combines the two sequential elements in a `Tuple2`. + +```kotlin:ank +ForOption extensions { + Some(5).mproduct { + Some(it * 11) + } +} +``` + +#### followedBy/followedByEval + +Executes sequentially two elements that are independent from one another. +The [`Eval`]({{ '/docs/datatypes/eval' | relative_url }}) variant allows you to pass lazily calculated values. + +```kotlin:ank +ForOption extensions { + Some(1).followedBy(Some(2)) +} +``` + +#### forEffect/forEffectEval + +Executes sequentially two elements that are independent from one another, ignoring the value of the second one. +The [`Eval`]({{ '/docs/datatypes/eval' | relative_url }}) variant allows you to pass lazily calculated values. + +```kotlin:ank +ForOption extensions { + Some(1).forEffect(Some(2)) +} +``` + +### Laws + +Arrow provides [`MonadLaws`][applicative_law_source]{:target="_blank"} in the form of test cases for internal verification of lawful instances and third party apps creating their own Applicative instances. + +#### Creating your own `Monad` instances + +Arrow already provides `Monad` instances for most common datatypes both in Arrow and the Kotlin stdlib. + +See [Deriving and creating custom typeclass]({{ '/docs/patterns/glossary' | relative_url }}) to provide your own `Monad` instances for custom datatypes. ### Data Types @@ -28,3 +129,9 @@ The following data types in Arrow provide instances that adhere to the `Monad` t - [EitherT]({{ '/docs/datatypes/eithert' | relative_url }}) - [Eval]({{ '/docs/datatypes/eval' | relative_url }}) - [Id]({{ '/docs/datatypes/id' | relative_url }}) +- [Observable]({{ '/docs/integrations/rx2' | relative_url }}) +- [Flowable]({{ '/docs/integrations/rx2' | relative_url }}) +- [Deferred]({{ '/docs/integrations/kotlinxcoroutines' | relative_url }}) +- [Flux]({{ '/docs/integrations/reactor' | relative_url }}) +- [Mono]({{ '/docs/integrations/reactor' | relative_url }}) +- [IO]({{ '/docs/effects/io' | relative_url }})