<!-- theme: uncover --> <!-- class: invert --> # Monads: <br/>What? Why? Where? When? --- ## Who's under the beard? --- <img src="https://hackmd.io/_uploads/rJhpp_Jnh.jpg" alt="Denis Dubo Chevalier" width="200" style="text-align: center; margin: 0 auto" /> Denis Dubo Chevalier - Staff software engineer :construction_worker: - :heart: Vim - :heart: Simple processes :train: --- ## Agenda --- - Monads: What are they? - Why do they matter? - Where should we use them? - When shall we use them? <small>Credit is due: This talk is heavily inspired by [John Gallager's talk on the same topic at RubyConf 2021 Denver](https://www.youtube.com/watch?v=bj7yH0KhEww)</small> --- :warning: I will be oversimplifying some of the underlying concepts. --- ## Monads: What are they? --- ### Definition --- > The monad \[...\] is nothing but a simple substance that enters into composites; simple, that is, without parts. And there must be simple substances, since there are composites \[...\] These monads are the true atoms of nature and, in a word, the elements of things. <small><a href="http://philosophyfaculty.ucsd.edu/faculty/rutherford/Leibniz/translations/Monadology.pdf">Leibniz, Monadology</a></small> --- Oops :open_mouth: --- Wrong monads :fire: --- > A monad is a monoid in the category of endofunctors. --- There, that's the one :smiley: --- But it's over simplified :confused: --- > All told, a monad in X is just a monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor. <small><a href="https://books.google.fr/books?id=MXboNPdTv7QC&pg=PA138&lpg=PA138&dq=%22monoid+in+the+category+of+endofunctors%22+mac+lane&source=bl&ots=feQWTkH2Uw&sig=tv-1JwaMOygKGmFE2vM2FhJVS9o&hl=en&ei=5iWsTJCkBIPSsAPQwJ36Aw&sa=X&oi=book_result&ct=result&redir_esc=y#v=onepage&q&f=false">Saunders, Categories for the Working Mathematician</a></small> --- Let's focus on the simplified definition for now :smile_cat: --- A monad is a what in the what of what :question: --- Don't worry, it's actually really simple! --- > A monad is a monoid in the category of endofunctors. --- ### What is a category? --- Let's do some maths! --- A **category** $\mathcal{C}$ consists of: <div style="font-size: .75em"> 1. A collection $obj(\mathcal{C})$ of objects; 2. For each pair $A,B \in obj(\mathcal{C})$, a collection $\mathcal{C}(A,B)$ of **morphisms** from $A$ to $B$, denoted $hom(\mathcal{C})$; 3. For every $A,B,C \in obj(\mathcal{C})$, a binary operation $\mathcal{C}(A,B) \times \mathcal{C}(B,C) \to \mathcal{C}(A,C)$ called **composition of morphisms**, denoted (for two morphisms $f:A \to B$ and $g: B \to C$) $g \circ f: A \to C$. <p style="text-align: left;margin-top:1ex">Satisfying <b>associativity</b> of morphism composition and <b>identity</b> morphisms for each morphism/object pair.</p> 💡 A morphism is often a function. </div> --- In plain english, it's a set of objects and operations on those objects that can move from one to another and support composition. --- For instance, the set of all integers and the arithmetic operators. Or a set of destinations, and the movement from one to another, etc. --- ### What is a monoid? --- First of all, a monoid is a category, but with some more constraints. --- Let's do some maths (again 😓). --- $$1 + 2 = 3$$ --- So we have two things of one kind, and when we apply some operation (a way of combining them), we get another thing of the same kind. --- Too simple? --- $$1 + 2 + 3 = 6$$ --- As we have the same kind in input and output, we can do it for more than two things. <small>So we can apply it to lists, it is **reducible** 🤓</small> --- Still too simple? --- $$(1 + 2) + 3 = 1 + (2 + 3)$$ --- We can put parenthesis however we want, it doesn't change the result. <small>So we can parallelize the computation, it is **parallelizable** 🤓</small> --- :warning: $$(1 - 2) - 3 \neq 1 - (2 - 3)$$ --- One last step! --- $$1 + 0 = 1$$ $$0 + 1 = 1$$ --- There is some value that make it return itself. <small>There is an **identity** value 🤓</small> --- That's it, here is a monoid: <small><ul> <li> A <b>category</b> of things that can be combined via an operation that gives another thing of the same type</li> <li> Where the combination is <b>reducible</b>.</li> <li> And where the combination is <b>parallelizable</b>.</li> <li> And where exists an <b>identity</b> value.</li> </ul></small> --- Like the set of all integers and the addition operation. <small>But not the substraction operation.</small> --- Simple! --- ### And what is an endofunctor? --- #### What is a functor? --- For all intents and purposes, it's a function object that takes an input and returns an output. <small>For the purists, there is more to it, a Functor is a **mapable** function object 🤓</small> --- #### What is an endofunctor? --- It's a functor that takes an input of a type and returns an output of the same type. --- ```ruby! square = ->(x) { x * x } ``` --- ### So, what's a Monad? --- > A Monad is just a monoid in the category of endofunctors --- To put it simply, a Monad is a **Chainable** construct. We use it for **composition**. --- And we should always chose composition over inheritance :wink: --- Let's see it in practice! --- ## Why do Monads matter? --- ### Let's see an example --- ```ruby! class FindContact def call(id) response = Rails.configuration.api .get("/contact/#{id}") JSON.parse(response.body).fetch('contact') rescue Faraday::Error, JSON::ParserError nil end end ``` --- Yeah! We handled all errors! --- But is our error handling any good? --- Let's see another example. --- ```ruby! class LookupPostdode def call(postcode) response = Rails.configuration .get("/addresses/#{postcode}") JSON.parse(response.body).fetch('addr') rescue Faraday::Error, JSON::ParserError [] end end ``` --- Now we return an empty list on error !? --- Let's see a controller for those two classes. --- ```ruby! class AddressesControler < ApplicationController def edit_addr contact = FindContact.new.call(params[:contact_id]) render :edit_with_error and return if contact.nil? addr = LookupPostcode.new .call(contact.fetch('postcode')) if addr.any? render :edit_with_address_select, locals: { addr: } else render :edit_with_manual_address_entry end end end ``` --- There are a few problems. --- We need to be aware of `nil`. --- > I call it my billion-dollar mistake. It was the invention of the null > reference in 1965. <small><a href="http://qconlondon.com/london-2009/speaker/Tony+Hoare">Tony Hoare, 2009</a></small> --- Have you tried to call a method on nil? It will blow up. <small> _With the exception of `.nil?`_</small> --- Also, we have a strong coupling between the controller and the service classes. --- ```ruby! class FindContact def call(id) response = Rails.configuration.api .get("/contact/#{id}") JSON.parse(response.body).fetch('contact') rescue Faraday::Error, JSON::ParserError nil end end ``` What is the difference between an inexisting contact and a timeout trying to get one? --- **We have no convention as to what the return value should be.** --- So we ship broken software :cry: --- ### Let's fix this code --- ## Where should I apply Monads? --- We'll do it in the same order: --- ```ruby! class FindContact include Dry::Monads[:result] def call(id) response = Rails.configuration.api .get("/contact/#{id}") Success(JSON.parse(response.body).fetch('contact')) rescue Faraday::Error, JSON::ParserError => e Failure(e) end end ``` --- Not much change, right? --- Let's do the same thing for `LookupPostcode` class. --- ```ruby! class LookupPostcode include Dry::Monads[:result] def call(postcode) response = Rails.configuration .get("/addresses/#{postcode}") Success(JSON.parse(response.body).fetch('addr')) rescue Faraday::Error, JSON::ParserError => e Failure(e) end end ``` --- Now let's see the power of those `Failure` and `Success` thingies. --- ```ruby! class AddressesControler < ApplicationController def edit_addr FindContact.new.call(params[:contact_id]) .or { |e| render :edit_with_error and return } .fmap do |result| LookupPostcode.new.call(result.fetch('postcode')) .or { |e| render :edit_with_manual_addr_entry } .fmap do |addr| render :edit_with_addr_select, locals: { addr: } end end end end ``` --- Nani? :confused: --- `Success` and `Failure` are two types that follow the same interface, `Result`. The `Result` interface is a **Monad**. It allows us to convert our program flow to a pipelines flow. <small>It's a combinator that allows chaining of operations.</small> --- Result defines a few methods: <small><ul> <li><code>Result.success?</code> Returns true if the value is a Success.</li> <li><code>Result.failure?</code> Returns true if the value is a Failure.</li> <li><code>Result.value!</code> Gives the underlying value if the value is a Success, throws an exception in case of failure.</li> </ul></small> --- We handle Success with `.fmap()`: <small> `fmap` stands for flat map, it is also synonymous to `bind`.</small> --- `fmap` takes a method of the underlying type and returns an object of the same type, wrapped into the result, if the result is a `Success`. It returns the original object otherwise. ```ruby! Success(42).fmap { |x| x * 2 } # => Success(84) Failure(:divide_by_zero).fmap { |x| x * 2 } # => Failure(:divide_by_zero) ``` --- We handle Failure with `.or()`: --- `or` takes a method on an error and applies it to the failure. It then returns the original object. It only returns the original object on success. ```ruby! Success(42).or{ |e| Airbrake.notify(e) } # => Success(42) # side effect: none Failure(:divide_by_zero).or{ |e| Airbrake.notify(e) } # => Failure(:divide_by_zero) # side effect: Airbrake.notify(:divide_by_zero) ``` --- And we can chain them! --- ### To recap --- We went from this: ```ruby! class AddressesControler < ApplicationController def edit_addr contact = FindContact.new.call(params[:contact_id]) render :edit_with_error and return if contact.nil? addr = LookupPostcode.new .call(contact.fetch('postcode')) if addr.any? render :edit_with_address_select, locals: { addr: } else render :edit_with_manual_address_entry end end end ``` --- To this: ```ruby! class AddressesControler < ApplicationController def edit_addr FindContact.new.call(params[:contact_id]) .or { |e| render :edit_with_error and return } .fmap do |result| LookupPostcode.new.call(result.fetch('postcode')) .or { |e| render :edit_with_manual_addr_entry } .fmap do |addr| render :edit_with_addr_select, locals: { addr: } end end end end ``` --- We have gained: - Cleaner code - Better error and exception handling - Better flow --- And I only showed you one application of the monads! --- ### So to answer the question, where should I use Monads? --- Whenever I have an object that could have two or more kinds of status: - A nilable, - A success/failure, - An asynchronous task, - Etc. --- Whenever I want to encapsulate side effects. --- Whenever I want composition over inheritance: Monads work by composition! --- Whenever I want to show a clear flow by chaining operations. --- Finally, when I want to be certain that I handle all possible failures. --- ## Ok, this is great, but I don't have time to do this right now --- That may be true. --- We won't change our code base in a day. --- But we can still learn from this pattern! --- And maybe apply it to some new code. --- ## :100: :muscle: :tada: --- ### Wrap up - Monads are simple :tada: - As a developer we should be wary of the flow of our programs; - And _KISS_. --- ## Thank you! :sheep: --- Going Further: <small><ul> <li><a href="https://dry-rb.org/gems/dry-monads/1.6/">The Dry::Monads documentation</a></li> <li><a href="https://github.com/denisdubochevalier/monad/">My own monad go package toy project</a></li> <li><a href="https://www.youtube.com/watch?v=Nrp_LZ-XGsY"> Scott Wlaschin's The Functional Programmer Toolkit talk</a></li> <li><a href="https://www.youtube.com/watch?v=3VQ382QG-y4">Gabriel Lebec's Fundamentals of Lambda Calculs talk</a></li> <li><a href="https://www.youtube.com/watch?v=pAnLQ9jwN-E">Gabriel Lebec's A Flock of Functions talk</a></li> </ul></small> --- $$Y=\lambda{f}.(\lambda{x}.f(x\ x))(\lambda{x}.f(x\ x))$$
{"breaks":true,"description":"Tech sharing session","title":"Monads: What? Why? Where? When?","contributors":"[{\"id\":\"4ac2068d-3dc3-414e-8fde-789582d34f1e\",\"add\":32572,\"del\":34134}]"}
    276 views