Related
I'm relatively new to studying functional programming and things were going well until I had to treat errors and promises. Trying to do it in the “right” way I get a lot of references for Monads as to be the better solution for it, but while studying it I ended up with what I honestly would call a "reference hell", where there was a lot of references and sub-references either mathematical or in programming for the same thing where this thing would have a different name for the same concepts, which was really confusing. So after persisting in the subject, I'm now trying to summarize and clarify it, and this is what I get so far:
For the sake of understanding I'll oversimplify it.
Monoids: are anything that concatenate/sum two things returning a thing of the same group so in JS any math addition or just concatenation from a string are Monoids for definition as well the composition of functions.
Maps: Maps are just methods that apply a function to each element of a group, without changing the category of the group itself or its length.
Functors: Functors are just objects that have a Map method that return the Functor itself.
Monads: Monads are Functors that use FlatMaps.
FlatMaps: FlatMaps are maps that have the capability of treat promises/fetch or summarize the received value.
Either, Maybe, Bind, Then: are all FlatMaps but with different names depending on the context you use it.
(I think they all are FlatMaps for definition but there is a difference in the way they are used since there is a library like Monets.js that have both a Maybe and a Either function, but I don’t get the use case difference).
So my question is: are these concepts right?
if anyone can reassure me what so far I got right and correct what I got wrong, or even expand on what I have missed, I would be very grateful.
Thanks to anyone who takes the time.
//=============================================================//
EDIT:
I should've emphasized more in this post, but these affirmations and simplified definitions are only from the "practical perspective in JavaScript" (I'm aware of the impossibility of making such a small simplification of a huge and complex theme like these especially if you add another field like mathematics).
//=============================================================//
Monoids: are anything that concatenate/sum two things returning a thing of the same group...
First thing that I don't like here is the word “group”. I know, you're just trying to use simple language and all, but the problem is that group has a very specific mathematical meaning, and we can't just ignore this because groups and monoids are very closely related. (A group is basically a monoid with inverse elements.) So definitely don't use this word in any definition of monoids, however informal. You could say “underlying set” there, but I'd just say type. That may not match the semantics in all programming languages, but certainly in Haskell.
So,
Monoids: are anything that concatenate/sum two things returning a thing of the same type so in JS any math addition or just concatenation from a string are Monoids for definition as well the composition of functions.
Ok. Specifically, concatenation of endofunctions is a monoid. In JavaScript, all functions are in a sense endofunctions, so you can get away with this.
But that's actually describing only a semigroup, not a monoid. (See, there's out groups... confusingly, monoids are in between semigroups and groups.) A monoid is a semigroup that has also a unit element, which can be concatenated to any other element without making a difference.
For the addition-monoid, this is the number zero.
For the string-monoid, it is the empty string.
For the function monoid, it is the identity function.
Even with unit elements, your characterization is missing the crucial feature of a semigroup/monoid: the concatenation operation must be associative. Associativity is a strangely un-intuitive property, perhaps because in most examples it seems stupidly obvious. But it's actually crucial for much of the maths that is built on those definitions.
To make the importance of associativity clear, it helps to look at some things that are not semigroups because they aren't associative. The type of integers with subtraction is such an example. The result is that you need to watch out where to put your parentheses in maths expressions, to avoid incurring sign errors. Whereas strings can just be concatenated in either order – ("Hello"+", ")+"World" is the same as "Hello"+(", "+"World").
Maps: Maps are just methods that apply a function to each element of a group, without changing the category of the group itself or its length.
Here we have the next badly chosen word: categories are again a specific maths thing that's very closely related to all we're talking about here, so please don't use the term with any other meaning.
IMO your definitions of “maps” and functors are unnecessary. Just define functors, using the already known concept of functions.
But before we can do that –
Functors: Functors are just objects...
here we go again, with the conflict between mathematical terminology and natural language. Mathematically, objects are the things that live in a category. Functors are not objects per se (although you can construct a specific category in which they are, by construction, objects). And also, itself already conflicting: in programming, “object” usually means “value with associated methods”, most often realized via a class.
Your usage of the terms seems to match neither of these established meanings, so I suggest you avoid it.
Mathematically, a functor is a mapping between two categories. That's hardly intuitive, but if you consider the category as a collection of types then a functor simply maps types to types. For example, the list functor maps some type (say, the type of integers) to the type of lists containing values of that type (the type of lists of integers).
Here of course we're running a bit into trouble when considering it all with respect to JS. In dynamic languages, you can easily have lists containing elements of multiple different types. But it's actually ok if we just treat the language as having only one big type that all values are members of. The list functor in Python maps the universal type to itself.
Blablatheory, what's the point of this all? The actual feature of a functor is not the type-mapping, but instead that it lifts a function on the contained values (i.e. on the values of the type you started with, in my example integers) to a function on the container-values (on lists of integers). More generally, the functor F lifts a function a -> b to a function F(a) -> F(b), for any types a and b. What you called “category of the group itself” means that you really are mapping lists to lists. Even in a dynamically typed language, the list functor's map method won't take a list and produce a dictionary as the result.
I suggest a different understandable-definition:
Functors: Functors wrap types as container-types, which have a mapping method that applies functions on contained values to functions on the whole container.
What you said about length is true of the list functor in particular, but it doesn't really make sense for functors in general. In Haskell we often talk about the fact that functor mapping preserves the “shape” of the container, but that too isn't actually part of the mathematical definition.
What is part of the definition is that a functor should be compatible with composition of the functions. This boils down to being able to map as often as you like. You can always map the identity function without changing the structure, and if you map two functions separately it has the same effect as mapping their composition in one go. It's kind of intuitive that this amounts to the mapping being “shape-preserving”.
Monads: Monads are Functors that use FlatMaps.
Fair enough, but of course this is just shifting everything to: what's a FlatMap?
Mathematically it's actually easier to not consider the FlatMap / >>= operation at first, but just consider the flattening operation, as well as the singleton injector. Going by example: the list monad is the list functor, equipped with
The operation that creates a list of just a plain contained value. (This is analogous to the unit value of a monoid.)
The operation that takes a nested list and flattens it out to a plain list, by gathering all the values in each of the inner lists. (This is analogous to the sum operation in a monoid.)
Again, it is important that these operations obey laws. These are also analogous to the monoid laws, but unfortunately even less intuitive because their simultaneously hard to think about and yet again so almost-trivial that they can seem a bit useless. But specifically the associativity law for lists can be phrased quite nicely:
Flattening the inner lists in a doubly nested list and then flattening the outer ones has the same effect as first flattening the outer ones and then the inner ones.
[[[1,2,3],[4,5]],[[6],[7,8,9]]] ⟼ [[1,2,3,4,5],[6,7,8,9]] ⟼ [1,2,3,4,5,6,7,8,9]
[[[1,2,3],[4,5]],[[6],[7,8,9]]]⟼[[1,2,3],[4,5],[6],[7,8,9]]⟼[1,2,3,4,5,6,7,8,9]
A Monoid is a set and an operator, such that:
The operator is associative for that set
The operator has an identity within that set
So, addition is associative for the set of real numbers, and in the set of real numbers has the identity zero.
a+(b+c) = (a+b)+c -- associative
a+0 = a -- identity
A Map is a transformation between two sets. For every element with the first set, there is a matching element in the second set. As an example, the transformation could be 'take a number and double it'.
The transformation is called a Functor. If the set is mapped back to itself, it is called an Endofunctor.
If an operator and a set is a Monoid, and also can be considered an Endofunctor, then we call that a Monad.
Monoids, Functors, Endofunctor, and Monads are not a thing but rather the property of a thing, that the operator and set has these properties. Can we declare this in Haskell by creating instances in the appropriate Monoid, Functor and Monad type-classes.
A FlatMap is a Map combined with a flattening operator. I can declare a map to be from a list to a list of lists. For a Monad we want to go from a list to a list, and so we flatten the list at the end to make it so.
To be blunt, I think all of your definitions are pretty terrible, except maybe the "monoid" one.
Here's another way of thinking about these concepts. It's not "practical" in the sense that it will tell you exactly why a flatmap over the list monad should flatten nested lists, but I think it's "practical" in the sense that it should tell you why we care about programming with monads in the first place, and what monads in general are supposed to accomplish from a practical perspective within functional programs, whether they are written in JavaScript or Haskell or whatever.
In functional programming, we write functions that take certain types as input and produce certain types as output, and we build programs by composing functions whose input and output types match. This is an elegant approach that results in beautiful programs, and it's one of the main reasons functional programmers like functional programming.
Functors provide a way to systematically transform types in a way that adds useful functionality to the original types. For example, we can use a functor to add functionality to a "normal type" that allows it to be missing or absent or "null" (Maybe) or represent either a successfully computed result or an error condition (Either) or that allows it to represent multiple possible values instead of only one (list) or that allows it to be computed at some time in the future (promise) or that requires a context for evaluation (Reader), or that allows a combination of these things.
A map allows us to reuse the functions we've defined for normal types on these new types that have been transformed by a functor, in some natural way. If we already have a function that doubles an integer, we can re-use that function on various functor transformations of integers, like doubling an integer that might be missing (mapping over a Maybe) or doubling an integer that hasn't been computed yet (mapping over a promise) or doubling every element of a list (mapping over a list).
A monad involves applying the functor concept to the output types of functions to produce "operations" that have additional functionality. With monad-less functional programming, we write functions that take "normal types" of inputs and produce "normal types" of outputs, but monads allow us to take "normal types" of inputs and produce transformed types of outputs, like the ones above. Such a monadic operation can represent a function that takes an input and Maybe produces an output, or one that takes an input and promises to produce an output later, or that takes an input and produces a list of outputs.
A flatmap generalizes the composition of functions on normal types (i.e., the way we build monad-less functional programs) to composition of monadic operations, appropriately "chaining" or combining the extra functionality provided by the transformed output types of the monadic operations. So, flatmaps over the maybe monad will compose functions as long as they keep producing outputs and give up when one of those functions has a missing output; flatmaps over the promise monad will turn a chain of operations that each take an input and promise an output into a single composed operation that takes an input and promises a final output; flatmaps over the list monad will turn a chain of operations that each take a single input and produce multiple outputs into a single composed operation that takes an input and produces multiple outputs.
Note that these concepts are useful because of their convenience and the systematic approach they take, not because they add magic functionality to functional programs that we wouldn't otherwise have. Of course we don't need a functor to create a list data type, and we don't need a monad to write a function that takes a single input and produces a list of outputs. It just ends up being useful thinking in terms of "operations that take an input and promise to produce either an error message or a list of outputs", compose 50 of those operations together, and end up with a single composed operation that takes an input and promises either an error message or a list of outputs (without requiring deeply nested lists of nested promises to be manually resolved -- hence the value of "flattening").
(In practical programming terms, monoids don't have that much to do with the rest of these, except to make hilarious in-jokes about the category of endofunctors. Monoids are just a systematic way of combining or "reducing" a bunch of values of a particular type into a single value of that type, in a manner that doesn't depend on which values are combined first or last.)
In a nutshell, functors and their maps allow us to add functionality to our types, while monads and their flatmaps provide a mechanism to use functors while retaining some semblance of the elegance of simple functional composition that makes functional programming so enjoyable in the first place.
An example might help. Consider the problem of performing a depth-first traversal of a file tree. In some sense, this is a simple recursive composition of functions. To generate a filetree() rooted at pathname, we need to call a function on the pathname to fetch its children(), and then we need to recursively call filetree() on those children(). In pseudo-JavaScript:
// to generate a filetree rooted at a pathname...
function filetree(pathname) {
// we need to get the children and generate filetrees rooted at their pathnames
filetree(children(pathname))
}
Obviously, though, this won't work as real code. For one thing, the types don't match. The filetree function should be called on a single pathname, but children(pathname) will return multiple pathnames. There are also some additional problems -- it's unclear how the recursion is supposed to stop, and there's also the issue that the original pathname appears to get lost in the shuffle as we jump right to its children and their filetrees. Plus, if we're trying to integrate this into an existing Node application with a promise-based architecture, it's unclear how this version of filetree could support the promise-based filesystem API.
But, what if there was a way to add functionality to the types involved while maintaining the elegance of this simple composition? For example, what if we had a functor that allowed us to promise to return multiple values (e.g., multiple child pathnames) while logging strings (e.g., parent pathnames) as a side effect of the processing?
Such a functor would, as I've said above, be a transformation of types. That means that it would transform a "normal" type, like a "integer", into a "promise for a list of integers together with a log of strings". Suppose we implement this as an object containing a single promise:
function M(promise) {
this.promise = promise
}
which when resolved will yield an object of form:
{
"data": [1,2,3,4] // a list of integers
"log": ["strings","that","have","been","logged"]
}
As a functor, M would have the following map function:
M.prototype = {
map: function(f) {
return this.promise.then((obj) => ({
data: obj.data.map(f),
log: obj.log
}))
}
}
which would apply a plain function to the promised data (without affecting the log).
More importantly, as a monad, M would have the following flatMap function:
M.prototype = {
...
flatMap: function(f) {
// when the promised data is ready
return new M(this.promise.then(function(obj) {
// map the function f across the data, generating promises
var promises = obj.data.map((d) => f(d).promise)
// wait on all promises
return Promise.all(promises).then((results) => ({
// flatten all the outputs
data: results.flatMap((result) => result.data),
// add to the existing log
log: obj.log.concat(results.flatMap((result) => result.log))
}))
}))
}
}
I won't explain in detail, but the idea is that if I have two monadic operations in the M monad, that take a "plain" input and produce an M-transformed output, representing a promise to provide a list of values together with a log, I can use the flatMap method on the output of the first operation to compose it with the second operation, yielding a composite operation that takes a single "plain" input and produces an M-transformed output.
By defining children as a monadic operation in the M monad that promises to take a parent pathname, write it to the log, and produce a list of the children of this pathname as its output data:
function children(parent) {
return new M(fsPromises.lstat(parent)
.then((stat) => stat.isDirectory() ? fsPromises.readdir(parent) : [])
.then((names) => ({
data: names.map((x) => path.join(parent, x)),
log: [parent]
})))
}
I can write the recursive filetree function almost as elegantly as the original above, as a flatMap-assisted composition of the children and recursively invoked filetree functions:
function filetree(pathname) {
return children(pathname).flatMap(filetree)
}
In order to use filetree, I need to "run" it to extract the log and, say, print it to the console.
// recursively list files starting at current directory
filetree(".").promise.then((x) => console.log(x.log))
The full code is below. Admittedly, there's a fair bit of it, and some of it is pretty complicated, so the elegance of the filetree function appears to have come at a fairly big cost, as we've apparently just moved all the complexity (and them some) into the M monad. However, the M monad is a general tool, not specific to performing depth-first traversals of file trees. Also, in an ideal world, a sophisticated JavaScript monad library would allow you to build the M monad from monadic pieces (promise, list, and log) with a couple lines of code.
var path = require('path')
var fsPromises = require('fs').promises
function M(promise) {
this.promise = promise
}
M.prototype = {
map: function(f) {
return this.promise.then((obj) => ({
data: obj.data.map(f),
log: obj.log
}))
},
flatMap: function(f) {
// when the promised data is ready
return new M(this.promise.then(function(obj) {
// map the function f across the data, generating promises
var promises = obj.data.map((d) => f(d).promise)
// wait on all promises
return Promise.all(promises).then((results) => ({
// flatten all the outputs
data: results.flatMap((result) => result.data),
// add to the existing log
log: obj.log.concat(results.flatMap((result) => result.log))
}))
}))
}
}
// not used in this example, but this embeds a single value of a "normal" type into the M monad
M.of = (x) => new M(Promise.resolve({ data: [x], log: [] }))
function filetree(pathname) {
return children(pathname).flatMap(filetree)
}
function children(parent) {
return new M(fsPromises.lstat(parent)
.then((stat) => stat.isDirectory() ? fsPromises.readdir(parent) : [])
.then((names) => ({
data: names.map((x) => path.join(parent, x)),
log: [parent]
})))
}
// recursively list files starting at current directory
filetree(".").promise.then((x) => console.log(x.log))
The related Question is
What is so special about Monads?
bind can be composed of fmap and join, so do we have to use monadic functions a -> m b?
In the first question:
What is so special about Monads?
A monad is a mathematical structure which is heavily used in (pure) functional programming, basically Haskell. However, there are many other mathematical structures available, like for example applicative functors, strong monads, or monoids. Some have more specific, some are more generic. Yet, monads are much more popular. Why is that?
The comment to reply the question:
As far as I recall, monads were popularised by Wadler, and at the time the idea of doing IO without tedious CPS and parsing without explicit state passing were huge selling points; it was a hugely exciting time. A.F.A.I.R., Haskell didn't do constructor classes, but Gofer (father of Hugs) did. Wadler proposed overloading list comprehension for monads, so the do notation came later. Once IO was monadic, monads became a big deal for beginners, cementing them as a major thing to grok. Applicatives are much nicer when you can, and Arrows more general, but they came later, and IO sells monads hard. – AndrewC May 9 '13 at 1:34
The answer by #Conal is:
I suspect that the disproportionately large attention given to this one particular type class (Monad) over the many others is mainly a historical fluke. People often associate IO with Monad, although the two are independently useful ideas (as are list reversal and bananas). Because IO is magical (having an implementation but no denotation) and Monad is often associated with IO, it's easy to fall into magical thinking about Monad.
First of all, I agree with them, and I think the usefulness of Monads mostly arises from Functors that we can embed many functions within the structure, and Monads is a little expansion for robustness of function composition by join : M(M(X)) -> M(X) to avoid the nested type.
In the 2nd Question:
do we have to use monadic functions a -> m b?
so many tutorials around the web still insist to use a monadic functions since that is the Kleisli triple and the monad-laws.
and many answers like
I like to think of such an m as meaning "plan-to-get", where "plans" involve some sort of additional interaction beyond pure computation.
or
In situations where Monad isn't necessary, it is often simpler to use Applicative, Functor, or just basic pure functions. In these cases, these things should be (and generally are) used in place of a Monad. For example:
ws <- getLine >>= return . words -- Monad
ws <- words <$> getLine -- Functor (much nicer)
To be clear: If it's possible without a monad, and it's simpler and more readable without a monad, then you should do it without a monad! If a monad makes the code more complex or confusing than it needs to be, don't use a monad! Haskell has monads for the sole purpose of making certain complex computations simpler, easier to read, and easier to reason about. If that's not happening, you shouldn't be using a monad.
Reading their answers, I suppose their special feeling about Monad arises from the historical incident that Haskell community has happend to chose Monads in Kleisli category to solve their problem(IO etc.)
So, again, I think the usefulness of Monads mostly arises from Functors that we can embed many functions within the structure, and Monads is a little expansion for robustness of function composition by join : M(M(X)) -> M(X) to avoid the nested type.
In fact, in JavaScript I implemented as below..
Functor
console.log("Functor");
{
const unit = (val) => ({
// contextValue: () => val,
fmap: (f) => unit((() => {
//you can do pretty much anything here
const newVal = f(val);
// console.log(newVal); //IO in the functional context
return newVal;
})()),
});
const a = unit(3)
.fmap(x => x * 2) //6
.fmap(x => x + 1); //7
}
The point is we can implement whatever we like in the Functor structure, and in this case, I simply made it IO/console.log the value.
Another point is, to do this Monads is absolutely unnecessary.
Monad
Now, based on the Functor implementation above, I add extra join: MMX => MX feature to avoid the nested structure that should be helpful for robustness of complex functional composition.
The functionality is exactly identical to the Functor above, and please note the usage is also identical to the Functor fmap. This does not require a "monadic function" to bind (Kleisli composition of monads).
console.log("Monad");
{
const unit = (val) => ({
contextValue: () => val,
bind: (f) => {
//fmap value operation
const result = (() => {
//you can do pretty much anything here
const newVal = f(val);
console.log(newVal);
return newVal;
})();
//join: MMX => MX
return (result.contextValue !== undefined)//result is MX
? result //return MX
: unit(result) //result is X, so re-wrap and return MX
}
});
//the usage is identical to the Functor fmap.
const a = unit(3)
.bind(x => x * 2) //6
.bind(x => x + 1); //7
}
Monad Laws
Just in case, this implementation of the Monad satisfies the monad laws, and the Functor above does not.
console.log("Monad laws");
{
const unit = (val) => ({
contextValue: () => val,
bind: (f) => {
//fmap value operation
const result = (() => {
//you can do pretty much anything here
const newVal = f(val);
//console.log(newVal);
return newVal;
})();
//join: MMX => MX
return (result.contextValue !== undefined)
? result
: unit(result)
}
});
const M = unit;
const a = 1;
const f = a => (a * 2);
const g = a => (a + 1);
const log = m => console.log(m.contextValue()) && m;
log(
M(f(a))//==m , and f is not monadic
);//2
console.log("Left Identity");
log(
M(a).bind(f)
);//2
console.log("Right Identity");
log(
M(f(a))//m
.bind(M)// m.bind(M)
);//2
console.log("Associativity");
log(
M(5).bind(f).bind(g)
);//11
log(
M(5).bind(x => M(x).bind(f).bind(g))
);//11
}
So, here is my question.
I may be wrong.
Is there any counter example that Functors cannnot do what Monads can do except the robustness of functional composition by flattening the nested structure?
What's so special about Monads in Kleisli category? It seems like it's fairly possible to implement Monads with a little expansion to avoid the nested structure of Functor and without the monadic functions a -> m b that is the entity in Kleisli category.
Thanks.
edit(2018-11-01)
Reading the answers, I agree it's not appropriate to perform console.log inside the IdentityFunctor that should satisfy Functor-laws, so I commented out like the Monad code.
So, eliminating that problem, my question still holds:
Is there any counter example that Functors cannnot do what Monads can do except the robustness of functional composition by flattening the nested structure?
What's so special about Monads in Kleisli category? It seems like it's fairly possible to implement Monads with a little expansion to avoid the nested structure of Functor and without the monadic functions a -> m b that is the entity in Kleisli category.
An answer from #DarthFennec is:
"Avoiding the nested type" is not in fact the purpose of join, it's just a neat side-effect. The way you put it makes it sound like join just strips the outer type, but the monad's value is unchanged.
I believe "Avoiding the nested type" is not just a neat side-effect, but a definition of "join" of Monad in category theory,
the multiplication natural transformation μ:T∘T⇒T of the monad provides for each object X a morphism μX:T(T(X))→T(X)
monad (in computer science): Relation to monads in category theory
and that's exactly what my code does.
On the other hand,
This is not the case. join is the heart of a monad, and it's what allows the monad to do things.
I know many people implements monads in Haskell in this manner, but the fact is, there is Maybe functor in Haskell, that does not has join, or there is Free monad that join is embedded from the first place into the defined structure. They are objects that users define Functors to do things.
Therefore,
You can think of a functor as basically a container. There's an arbitrary inner type, and around it an outer structure that allows some variance, some extra values to "decorate" your inner value. fmap allows you to work on the things inside the container, the way you would work on them normally. This is basically the limit of what you can do with a functor.
A monad is a functor with a special power: where fmap allows you to work on an inner value, bind allows you to combine outer values in a consistent way. This is much more powerful than a simple functor.
These observation does not fit the fact of the existence of Maybe functor and Free monad.
Is there any counter example that Functors cannnot do what Monads can do except the robustness of functional composition by flattening the nested structure?
I think this is the important point:
Monads is a little expansion for robustness of function composition by join : M(M(X)) -> M(X) to avoid the nested type.
"Avoiding the nested type" is not in fact the purpose of join, it's just a neat side-effect. The way you put it makes it sound like join just strips the outer type, but the monad's value is unchanged. This is not the case. join is the heart of a monad, and it's what allows the monad to do things.
You can think of a functor as basically a container. There's an arbitrary inner type, and around it an outer structure that allows some variance, some extra values to "decorate" your inner value. fmap allows you to work on the things inside the container, the way you would work on them normally. This is basically the limit of what you can do with a functor.
A monad is a functor with a special power: where fmap allows you to work on an inner value, bind allows you to combine outer values in a consistent way. This is much more powerful than a simple functor.
The point is we can implement whatever we like in the Functor structure, and in this case, I simply made it IO/console.log the value.
This is incorrect, actually. The only reason you were able to do IO here is because you're using Javascript, and you can do IO anywhere. In a purely functional language like Haskell, IO cannot be done in a functor like this.
This is a gross generalization, but for the most part it's useful to describe IO as a glorified State monad. Each IO action takes an extra hidden parameter called RealWorld (which represents the state of the real world), maybe reads from it or modifies it, and then sends it on to the next IO action. This RealWorld parameter is threaded through the chain. If something is written to the screen, that's RealWorld being copied, modified, and passed along. But how does the "passing along" work? The answer is join.
Say we want to read a line from the user, and print it back to the screen:
getLine :: IO String
putStrLn :: String -> IO ()
main :: IO ()
main = -- ?
Let's assume IO is a functor. How do we implement this?
main :: IO (IO ())
main = fmap putStrLn getLine
Here we've lifted putStrLn to IO, to get fmap putStrLn :: IO String -> IO (IO ()). If you recall, putStrLn takes a String and a hidden RealWorld and returns a modified RealWorld, where the String parameter is printed to the screen. We've lifted this function with fmap, so that it now takes an IO (which is an action that takes a hidden RealWorld, and returns a modified RealWorld and a String), and returns the same io action, just wrapped around a different value (a completely separate action that also takes a separate hidden RealWorld and returns a RealWorld). Even after applying getLine to this function, nothing actually happens or gets printed.
We now have a main :: IO (IO ()). This is an action that takes a hidden RealWorld, and returns a modified RealWorld and a separate action. This second action takes a different RealWorld and returns another modified RealWorld. This on its own is pointless, it doesn't get you anything and it doesn't print anything to the screen. What needs to happen is, the two IO actions need to be connected together, so that one action's returned RealWorld gets fed in as the other action's RealWorld parameter. This way it becomes one continuous chain of RealWorlds that mutate as time goes on. This "connection" or "chaining" happens when the two IO actions are merged with join.
Of course, join does different things depending on which monad you're working with, but for IO and State-type monads, this is more or less what's happening under the hood. There are plenty of situations where you're doing something very simple that doesn't require join, and in those cases it's easy to treat the monad as a functor or applicative functor. But usually that isn't enough, and in those cases we use monads.
EDIT: Responses to comments and edited question:
I don't see any definition of Monads in categort theory explains this. All I read about join is stil MMX => MX and that is exactly what my code does.
Can you also tell exactly what a function String -> String does? Might it not return the input verbatim, reverse it, filter it, append to it, ignore it and return a completely different value, or anything else that results in a String? A type does not determine what a function does, it restricts what a function can do. Since join in general is only defined by its type, any particular monad can do anything allowed by that type. This might just be stripping the outer layer, or it might be some extremely complex method of combining the two layers into one. As long as you start with two layers and end up with one layer, it doesn't matter. The type allows for a number of possibilities, which is part of what makes monads so powerful to begin with.
There is MaybeFunctor in Haskell. There's no "join" or "bind" there, and I wonder from where the power come. What is the difference between MaybeFunctor and MaybeMonad?
Every monad is also a functor: a monad is nothing more than a functor that also has a join function. If you use join or bind with a Maybe, you're using it as a monad, and it has the full power of a monad. If you do not use join or bind, but only use fmap and pure, you're using it as a functor, and it becomes limited to doing the things a functor can do. If there's no join or bind, there is no extra monad power.
I believe "Avoiding the nested type" is not just a neat side-effect, but a definition of "join" of Monad in category theory
The definition of join is a transformation from a nested monad to a non-nested monad. Again, this could imply anything. Saying the purpose of join is "to avoid the nested type" is like saying the purpose of + is to avoid pairs of numbers. Most operations combine things in some way, but very few of those operations exist simply for the sake of having a combination of things. The important thing is how the combining happens.
there is Maybe functor in Haskell, that does not has join, or there is Free monad that join is embedded from the first place into the defined structure. They are objects that users define Functors to do things.
I've already discussed Maybe, and how when you use it as a functor only, it can't do the things it can do if you use it as a monad. Free is weird, in that it's one of the few monads that doesn't actually do anything.
Free can be used to turn any functor into a monad, which allows you to use do notation and other conveniences. However, the conceit of Free is that join does not combine your actions the way other monads do, instead it keeps them separate, inserting them into a list-like structure; the idea being that this structure is later processed and the actions are combined by separate code. An equivalent approach would be to move that processing code into join itself, but that would turn the functor into a monad and there would be no point in using Free. So the only reason Free works is because it delegates the actual "doing things" part of the monad elsewhere; its join opts to defer action to code running outside the monad. This is like a + operator that, instead of adding the numbers, returns an abstract syntax tree; one could then process that tree later in whatever way is needed.
These observation does not fit the fact of the existence of Maybe functor and Free monad.
You are incorrect. As explained, Maybe and Free fit perfectly into my previous observations:
The Maybe functor simply does not have the same expressiveness as the Maybe monad.
The Free monad transforms functors into monads in the only way it possibly can: by not implementing a monadic behavior, and instead simply deferring it to some assumed processing code.
The point is we can implement whatever we like in the Functor structure, and in this case, I simply made it IO/console.log the value.
Another point is, to do this Monads is absolutely unnecessary.
The problem is that once you do that your functor is no longer a functor. Functors should preserve identities and composition. For Haskell Functors, those requirements amount to:
fmap id = id
fmap (g . f) = fmap g . fmap f
Those laws are a guarantee that all fmap does is using the supplied function to modify values -- it doesn't do funny stuff behind your back. In the case of your code, fmap(x => x) should do nothing; instead, it prints to the console.
Note that all of the above applies to the IO functor: if a is an IO action, executing fmap f a will have no I/O effects other than those a already had. One stab at writing something similar in spirit to your code might be...
applyAndPrint :: Show b => (a -> b) -> a -> IO b
applyAndPrint f x = let y = f x in fmap (const y) (print y)
pseudoFmap :: Show b => (a -> b) -> IO a -> IO b
pseudoFmap f a = a >>= applyAndPrint f
... but that makes use of Monad already, as we have an effect (printing a result) which depends on the result of a previous computation.
It goes without saying that if you are so inclined (and your type system allows it) you can write code that disregards all of those distinctions. There is, however, a trade-off: the decreased power of Functor with respect to Monad comes with extra guarantees on what functions using the interface can and cannot do -- that is what makes the distinctions useful in the first place.
Your “functor” is very manifestly not a functor, violating both the identity and composition law:
console.log("Functor");
{
const unit = (val) => ({
// contextValue: () => val,
fmap: (f) => unit((() => {
//you can do pretty much anything here
const newVal = f(val);
console.log(newVal); //IO in the functional context
return newVal;
})()),
});
console.log("fmap(id) ...");
const a0 = unit(3)
.fmap(x => x); // prints something
console.log(" ≡ id?");
const a1 = (x => x)(unit(3)); // prints nothing
console.log("fmap(f) ∘ fmap(g) ...");
const b0 = unit(3)
.fmap(x => 3*x)
.fmap(x => 4+x); // prints twice
console.log(" ≡ fmap(f∘g)?");
const b1 = unit(3)
.fmap(x => 4+(3*x)); // prints once
}
overly long comment:
I would suggest forgetting about Kleisli categories for now; I don't believe they have anything to do with your confusion.
Also while I still don't fully understand your question and assertions, some context that might be useful: category theory is extremely general and abstract; the concepts like Monad and Functor as they exist in haskell are (necessarily) somewhat less general and less abstract (e.g. the notion of the category of "Hask").
As a general rule the more concrete (the less abstract) a thing becomes, the more power you have: if I tell you you have a vehicle then you know you have a thing that can take you from one place to another, but you don't know how fast, you don't know whether it can go on land, etc. If I tell you you have a speed boat then there opens up a whole larger world of things you can do and reason about (you can use it to catch fish, you know that it won't get you from NYC to Denver).
When you say:
What's so special about Monads in Kleisli category?
...I believe you're making the mistake of suspecting that the conception of Monad and Functor in haskell is in some way more restrictive relative to category theory but, as I try to explain by analogy above, the opposite is true.
Your code is the same sort of flawed thinking: you model a speedboat (which is a vehicle) and claim it shows that all vehicles are fast and travel on water.
I have some ugly data, that requires a lot of ugly null checks. My goal is to write a suite of functions to access/modify it in a point-free, declarative style, using the Maybe monad to keep null checks to a minimum. Ideally I would be able to use Ramda with the monads, but it's not working out so great.
This works:
const Maybe = require('maybe');
const R = require('ramda');
const curry = fn => (...args) => fn.bind(null, ...args);
const map = curry((fn, monad) => (monad.isNothing()) ? monad : Maybe(fn(monad.value())));
const pipe = (...fns) => acc => fns.reduce((m, f) => map(f)(m), acc);
const getOrElse = curry((opt, monad) => monad.isNothing() ? opt : monad.value());
const Either = (val, d) => val ? val : d;
const fullName = (person, alternative, index) => R.pipe(
map(R.prop('names')),
map(R.nth(Either(index, 0))),
map(R.prop('value')),
map(R.split('/')),
map(R.join('')),
getOrElse(Either(alternative, ''))
)(Maybe(person));
However, having to type out 'map()' a billion times doesn't seem very DRY, nor does it look very nice. I'd rather have a special pipe/compose function that wraps each function in a map().
Notice how I'm using R.pipe() instead of my custom pipe()? My custom implementation always throws an error, 'isNothing() is not a function,' upon executing the last function passed to it.
I'm not sure what went wrong here or if there is a better way of doing this, but any suggestions are appreciated!
first things first
that Maybe implementation (link) is pretty much junk - you might want to consider picking an implementation that doesn't require you to implement the Functor interface (like you did with map) – I might suggest Data.Maybe from folktale. Or since you're clearly not afraid of implementing things on your own, make your own Maybe ^_^
Your map implementation is not suitably generic to work on any functor that implements the functor interface. Ie, yours only works with Maybe, but map should be generic enough to work with any mappable, if there is such a word.
No worries tho, Ramda includes map in the box – just use that along with a Maybe that implements the .map method (eg Data.Maybe referenced above)
Your curry implementation doesn't curry functions quite right. It only works for functions with an arity of 2 – curry should work for any function length.
// given, f
const f = (a,b,c) => a + b + c
// what yours does
curry (f) (1) (2) (3) // => Error: curry(...)(...)(...) is not a function
// because
curry (f) (1) (2) // => NaN
// what it should do
curry (f) (1) (2) (3) // => 6
There's really no reason for you to implement curry on your own if you're already using Ramda, as it already includes curry
Your pipe implementation is mixing concerns of function composition and mapping functors (via use of map). I would recommend reserving pipe specifically for function composition.
Again, not sure why you're using Ramda then reinventing a lot of it. Ramda already includes pipe
Another thing I noticed
// you're doing
R.pipe (a,b,c) (Maybe(x))
// but that's the same as
R.pipe (Maybe,a,b,c) (x)
That Either you made is probably not the Either functor/monad you're thinking of. See Data.Either (from folktale) for a more complete implementation
Not a single monad was observed – your question is about function composition with monads but you're only using functor interfaces in your code. Some of the confusion here might be coming from the fact that Maybe implements Functor and Monad, so it can behave as both (and like any other interface it implements) ! The same is true for Either, in this case.
You might want to see Kleisli category for monadic function composition, though it's probably not relevant to you for this particular problem.
functional interfaces are governed by laws
Your question is born out of a lack of exposure/understanding of the functor laws – What these mean is if your data type adheres to these laws, only then can it can be said that your type is a functor. Under all other circumstances, you might be dealing with something like a functor, but not actually a functor.
functor laws
where map :: Functor f => (a -> b) -> f a -> f b, id is the identity function a -> a, and f :: b -> c and g :: a -> b
// identity
map(id) == id
// composition
compose(map(f), map(g)) == map(compose(f, g))
What this says to us is that we can either compose multiple calls to map with each function individually, or we can compose all the functions first, and then map once. – Note on the left-hand side of the composition law how we call .map twice to apply two functions, but on the right-hand side .map was only called once. The result of each expression is identical.
monad laws
While we're at it, we can cover the monad laws too – again, if your data type obeys these laws, only then can it be called a monad.
where mreturn :: Monad m => a -> m a, mbind :: Monad m => m a -> (a -> m b) -> m b
// left identity
mbind(mreturn(x), f) == f(x)
// right identity
mbind(m, mreturn) == m
// associativity
mbind(mbind(m, f), g) == mbind(m, x => mbind(f(x), g))
It's maybe even a little easier to see the laws using Kleisli composition function, composek – now it's obvious that Monads truly obey the associativity law
monad laws defined using Kleisli composition
where composek :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
// kleisli left identity
composek(mreturn, f) == f
// kleisli right identity
composek(f, mreturn) == f
// kleisli associativity
composek(composek(f, g), h) == composek(f, composek(g, h))
finding a solution
So what does all of this mean for you? In short, you're doing more work than you have to – especially implementing a lot of the things that already comes with your chosen library, Ramda. Now, there's nothing wrong with that (in fact, I'm a huge proponent of this if you audit many of my
other answers on the site), but it can be the source of confusion if you get some of the implementations wrong.
Since you seem mostly hung up on the map aspect, I will help you see a simple transformation. This takes advantage of the Functor composition law illustrated above:
Note, this uses R.pipe which composes left-to-right instead of right-to-left like R.compose. While I prefer right-to-left composition, the choice to use pipe vs compose is up to you – it's just a notation difference; either way, the laws are fulfilled.
// this
R.pipe(map(f), map(g), map(h), map(i)) (Maybe(x))
// is the same as
Maybe(x).map(R.pipe(f,g,h,i))
I'd like to help more, but I'm not 100% sure what your function is actually trying to do.
starting with Maybe(person)
read person.names property
get the first index of person.names – is it an array or something? or the first letter of the name?
read the .value property?? We're you expecting a monad here? (look at .chain compared to .map in the Maybe and Either implementations I linked from folktale)
split the value on /
join the values with ''
if we have a value, return it, otherwise return some alternative
That's my best guess at what's going on, but I can't picture your data here or make sense of the computation you're trying to do. If you provide more concrete data examples and expected output, I might be able to help you develop a more concrete answer.
remarks
I too was in your boat a couple of years ago; just getting into functional programming, I mean. I wondered how all the little pieces could fit together and actually produce a human-readable program.
The majority of benefits that functional programming provides can only be observed when functional techniques are applied to an entire system. At first, it will feel like you had to introduce tons of dependencies just to rewrite one function in a "functional way". But once you have those dependencies in play in more places in your program, you can start slashing complexity left and right. It's really cool to see, but it takes a while to get your program (and your head) there.
In hindsight, this might not be a great answer, but I hope this helped you in some capacity. It's a very interesting topic to me and I'm happy to assist in answering any other questions you have ^_^
According to my understanding of functional programming, you should be able to chain multiple functions and then execute the whole chain by going through the input data once.
In other words, when I do the following (pseudo-code):
list = [1, 2, 3];
sum_squares = list
.map(function(item) { return item * item; })
.reduce(function(total, item) { return total + item; }, 0);
I expect that the list will be traversed once, when each value will be squared and then everything will be added up (hence, the map operation would be called as needed by the reduce operation).
However, when I look at the source code of Underscore.js, I see that all the "functional programming" functions actually produce intermediate collections like, for example, so:
// Return the results of applying the iteratee to each element.
_.map = _.collect = function(obj, iteratee, context) {
iteratee = cb(iteratee, context);
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length,
results = Array(length);
for (var index = 0; index < length; index++) {
var currentKey = keys ? keys[index] : index;
results[index] = iteratee(obj[currentKey], currentKey, obj);
}
return results;
};
So the question is, as stated in the title, are we fooling ourselves that we do functional programming when we use Underscore.js?
What we actually do is make program look like functional programming without actually it being functional programming in fact. Imagine, I build a long chain of K filter() functions on list of length N, and then in Underscore.js my computational complexity will be O(K*N) instead of O(N) as would be expected in functional programming.
P.S. I've heard a lot about functional programming in JavaScript, and I was expecting to see some functions, generators, binding... Am I missing something?
Is Underscore.js functional programming a fake?
No, Underscore does have lots of useful functional helper functions. But yes, they're doing it wrong. You may want to have a look at Ramda instead.
I expect that the list will be traversed once
Yes, list will only be traversed once. It won't be mutated, it won't be held in memory (if you had not a variable reference to it). What reduce traverses is a different list, the one produced by map.
All the functions actually produce intermediate collections
Yes, that's the simplest way to implement this in a language like JavaScript. Many people rely on map executing all its callbacks before reduce is called, as they use side effects. JS does not enforce pure functions, and library authors don't want to confuse people.
Notice that even in pure languages like Haskell an intermediate structure is built1, though it would be consumed lazily so that it never is allocated as a whole.
There are libraries that implement this kind of optimisation in strict languages with the concept of transducers as known from Clojure. Examples in JS are transduce, transducers-js, transducers.js or underarm. Underscore and Ramda have been looking into them2 too.
I was expecting to see some […] generators
Yes, generators/iterators that can be consumed lazily are another choice. You'll want to have a look at Lazy.js, highland, or immutable-js.
[1]: Well, not really - it's a too easy optimisation
[2]: https://github.com/jashkenas/underscore/issues/1896, https://github.com/ramda/ramda/pull/865
Functional programming has nothing to do with traversing a sequence once; even Haskell, which is as pure as you're going to get, will traverse the length of a strict list twice if you ask it to filter pred (map f x).
Functional programming is a simpler model of computation where the only things that are allowed to happen do not include side effects. For example, in Haskell basically only the following things are allowed to happen:
You can apply a value f to another value x, producing a new value f x with no side-effects. The first value f is called a "function". It must be the case that any time you apply the same f to the same x you get the same answer for f x.
You can give a name to a value, which might be a function or a simple value or whatever.
You can define a new structure for data with a new type signature, and/or structure some data with those "constructors."
You can define a new type-class or show how an existing data structure instantiates a type-class.
You can "pattern match" a data structure, which is a combination of a case dispatch with naming the parts of the data structure for the rest of your project.
Notice how "print something to the console" is not doable in Haskell, nor is "alter an existing data structure." To print something to the console, you construct a value which represents the action of printing something to the console, and then give it a special name, main. (When you're compiling Haskell, you compute the action named main and then write it to disk as an executable program; when you run the program, that action is actually completed.) If there is already a main program, you figure out where you want to include the new action in the existing actions of that program, then use a function to sequence the console logging with the existing actions. The Haskell program never does anything; it just represents doing something.
That is the essence of functional programming. It is weaker than normal programming languages where the language itself does stuff, like JavaScript's console.log() function which immediately performs its side effect whenever the JS interpreter runs through it. In particular, there are some things which are (or seem to be) O(1) or O(log(log(n))) in normal programs where our best functional equivalent is O(log(n)).
Most resources on reactive programming, e.g. "A Survey on Reactive Programming" (Bainomugisha et al., 2012), introduce several characteristics by which different reactive solutions can be classified. One of these is which evaluation model is employed, that is, whether the reactive language (or library) is push-based or pull-based (or both).
Example: Let's take a look a this piece of pseudocode: var c = a + b; In a reactive environment, c is expected to always contain the sum of a and b. Accordingly, if a or b change, the value of c has to be recomputed. In a push-based solution, a and b notify c when their values have changed, so that c's value can be recomputed instantly. In a pull-based solution, c looks up the current values of a and b as soon as the value c itself is being demanded. Thus, all recomputations are being delayed until c's value is requested.
For quite some time now, I have tried to figure out what evaluation model is being employed by the JavaScript library Bacon.js. Let's assume the following:
var a = Bacon.constant(21); // Creates a Property
var b = Bacon.constant(21);
var c = a.combine(b, function (x, y) {
return x + y;
});
According to this section of the Bacon.js documentation, combinators such as combine use lazy evaluation, that is, they "avoid evaluating values that aren't actually needed".
Question 1: When are values "actually" needed?
Question 2: Is it safe to assume that--at least in the case of the example above--Bacon.js employs a pull-based approach? Or am I mixing things up here?
However, towards the end of the section, the Bacon.js documentation mentions that if one wants "[t]o force evaluation at the time of original event, [one] can just use flatMap". Really, now I am confused:
Question 3: Respectively, is it safe to assume that Bacon.js employs a push-based evaluation model whenever I use flatMap? Or am I mixing things up here--again?
Question 4: I cannot really reproduce the example from above using flatMap instead of combine. Does that mean that depending on what I am trying to do, Bacon.js sometimes employs a pull-based propagation of change, and sometimes it comes up with a push-based one?
To the one who can provide clarification, I would be very greatful. :P
Value is "needed" when a Subscriber calls the value() method of an event. This happens in all .on Value subscribers for instance. Combinators like map and combine do not evaluate the input values until their respective value is required. On the other hand, flatMap(f) needs to evaluate, because it uses the value to spawn a new stream using the provided function f. Also scan is currently eager.
As described above, it's a mixture of push and pull in the sense that evaluation occurs when necessary. In fact, Bacon.js is not based on any research papers and does not comply with any theoretical models.
No, don't assume any models. Instead, use referentially transparent functions and let the system decide when it needs to evaluate them.
Surely you cannot reproduce the same functionality with flatMap that you can with combine.