Some people have said a is a . Others have said a is not a . They are both wrong... and they are both right. Promise Monad Promise Monad By the time you finish reading this article, you will understand what a and are and how they are similar and different from a . Functor Monad Promise ☝️ Article originally posted at: . Read it on DEV.to if you want to see the code colorized. https://dev.to/joelnet/functional-javascript---functors-monads-and-promises-1pol Why can’t anyone explain a Monad? It is difficult to explain what a Monad is without also having the prerequisite vocabulary also required to understand it. I love this video with Richard Feynman when he is asked to describe “what is going on” between two magnets. The whole video is amazing and mind blowing, but you can skip straight to 6:09 if you have some sort of aversion to learning. I can’t explain that attraction in terms of anything else that’s familiar to you — Richard Feynman @ 6:09 So let’s backup a few steps and learn the vocabulary required to understand what a is. Monad Are we ready to understand a Functor? Definition: A `Functor` is something that is `Mappable` or something that can be mapped between objects in a Category. Okay… Not yet. But do not be afraid, you are already familiar with if you have used 's function. Functors Array map [1, 2, 3].map(x => x * 2) //=> [2, 4, 6] Before we can fully understand a , we also have to understand what it means to be and to understand that we also have to understand what a is. So let's begin there. Functor Mappable Category Categories, Object and Maps (Morphisms) A consists of a collection of nodes (objects) and morphisms (functions). An object could be numbers, strings, urls, customers, or any other way you wish to organize like-things. (X, Y, and Z in the graphic are the objects.) category A is a function to convert something from one object to another. (f, g, and fog are the maps). 🔍 Google tip: A between objects is called a . map map Morphism Example: An object in the object can be converted into the object using the method. Number Type String Type toString() // A map of Number -> Stringconst numberToString = num => num.toString() You can also create back into their own objects or more complex object types. maps // A map of Number -> Numberconst double = num => num * 2 // A map of Array -> Numberconst arrayToLength = array => array.length // A map of URL -> Promise (JSON)const urlToJson = url =>fetch(url).then(response => response.json()) So an object could be simple like a Number or a String. An object could also be more abstract like a Username, A User API URL, User API HTTP Request, User API Response, User API Response JSON. Then we can create maps or morphisms between each object to get the data we want. Examples of morphisms: Username -> User API Url User API Url -> User API HTTP Request User API HTTP Request -> User API Response User API Response -> User API Response JSON 🔍 Google tip: is a way to combining multiple or to create new . Using we could create a map from directly to Function Composition map morphisms maps Function Composition Username User API Response JSON Back to the Functor Now that we understand what it means to be , we can finally understand what a is. Mappable Functor A is something that is or something that can be mapped between objects in a Category. Functor Mappable An is , so it is a . In this example I am taking an and morphing it into an . Array Mappable Functor Array of Numbers Array of Strings const numberToString = num => num.toString() const array = [1, 2, 3] array.map(numberToString)//=> ["1", "2", "3"] Note: One of the properties of a is that they always stay that same type of . You can morph an containing to or any other object, but the will ensure that it will always be an . You cannot an of to just a . Functor Functor Array Strings Numbers map Array map Array Number Number We can extend this usefulness to other objects too! Let's take this simple example of a . Mappable Thing const Thing = value => ({value}) If we wanted to make mappable in the same way that is mappable, all we have to do is give it a function. Thing Array map const Thing = value => ({value,map: morphism => Thing(morphism(value))// ----- -------- -----// / | \// always a Thing | value to be morphed// |// Morphism passed into map}) const thing1 = Thing(1) // { value: 1 }const thing2 = thing1.map(x => x + 1) // { value: 2 } And that is a ! It really is just that simple. Functor 🔍 Google tip: The we created is known as . "Thing" Functor Identity Back to the Monad Sometimes functions return a value already wrapped. This could be inconvenient to use with a because it will re-wrap the in another . Functor Functor Functor const getThing = () => Thing(2) const thing1 = Thing(1) thing1.map(getThing) //=> Thing (Thing ("Thing 2")) This behavior is identical to 's behavior. Array const doSomething = x => [x, x + 100]const list = [1, 2, 3] list.map(doSomething) //=> [[1, 101], [2, 102], [3, 103]] This is where comes in handy. It's similar to , except the morphism is also expected to perform the work of wrapping the value. flatMap map const Thing = value => ({value,map: morphism => Thing(morphism(value)),flatMap: morphism => morphism(value)}) const thing1 = Thing(1) //=> Thing (1)const thing2 = thing1.flatMap(x => Thing(x + 1)) //=> Thing (2) That looks better! This could come in handy in a when you might need to switch from a to a , when for example a prop is missing. Maybe Just Nothing import Just from 'mojiscript/type/Just'import Nothing from 'mojiscript/type/Nothing' const prop = (prop, obj) =>prop in obj? Just(obj[prop]): Nothing Just({ name: 'Moji' }).flatMap(x => prop('name', x))//=> Just ("Moji") Just({}).flatMap(x => prop('name', x))//=> Nothing This code could be shortened to: const Just = require('mojiscript/type/Just')const Nothing = require('mojiscript/type/Nothing')const { fromNullable } = require('mojiscript/type/Maybe') const prop = prop => obj => fromNullable(obj[prop]) Just({ name: 'Moji' }).flatMap(prop('name'))//=> Just ("Moji") Just({}).flatMap(prop('name'))//=> Nothing 🔍 Google tip: This code shortening is made possible with , , and a . currying partial application point-free style I hope at this point you are thinking this was an easier journey than you initially thought it would be. We have covered and and next up in the ! Functors Monads Promise The Promise If any of that code looks familiar it’s because the behaves like both and . Promise map flatMap const double = num => num * 2 const thing1 = Thing(1) //=> Thing (1)const promise1 = Promise.resolve(1) //=> Promise (1) thing1.map(double) //=> Thing (2)promise1.then(double) //=> Promise (2) thing1.flatMap(x => Thing(double(x)))//=> Thing (2) promise1.then(x => Promise.resolve(double(x)))//=> Promise (2) As you can see the method works like when an unwrapped value is returned and works like , when it is wrapped in a . In this way a is similar to both a and a . Promise then map flatMap Promise Promise Functor Monad This is also the same way it differs. thing1.map(x => Thing(x + 1)) // Thing (Thing (2))promise1.then(x => Promise.resolve(x + 1)) // Promise (2) thing1.flatMap(x => x + 1) //=> 2promise1.then(x => x + 1) //=> Promise (2) If I wanted to wrap a value twice (think nested ) or control the return type, I am unable to with . In this way, it breaks the laws and also breaks the laws. Arrays Promise Functor Monad Summary A is something that is or something that can be mapped between objects in a Category. Functor Mappable A is similar to a , but is between Categories. Monad Functor Flat Mappable is similar to , but yields control of the wrapping of the return type to the mapping function. flatMap map A Promise breaks the and laws, but still has a lot of similarities. Same same but different. Functor Monad Continue reading: NULL, “The Billion Dollar Mistake”, Maybe Just Nothing My articles show massive Functional JavaScript love. If you need more FP, follow me here or on Twitter ! @joelnet And thanks to my buddy Joon for proofing this :)
Share Your Thoughts