11

I don't use Haskell a lot, but I understand the concept of Monads.

I had been confused by Kleisli triple, and the category, however,

fmap and join

Although Haskell defines monads in terms of the return and bind functions, it is also possible to define a monad in terms of return and two other operations, join and fmap. This formulation fits more closely with the original definition of monads in category theory. The fmap operation, with type (t → u) → M t → M u, takes a function between two types and produces a function that does the "same thing" to values in the monad. The join operation, with type M (M t) → M t, "flattens" two layers of monadic information into one.

helps me to the background principle of Monads.

The two formulations are related as follows:

fmap f m = m >>= (return . f)
join n   = n >>= id

fmap :: (a -> b) -> (m a -> m b)
unit :: a -> m a
join :: m (m a) -> m a
>>=  :: m a -> (a -> m b) -> m b

m >>= f  =  join $ fmap f m

My question is: I think that since >>= can be composed of fmap and join, a monadic function a -> m b is not required and normal functions a -> b will satisfy the operation, but so many tutorials around the web still insist to use a monadic functions since that is the Kleisli triple and the monad-laws.

Well, shouldn't we just use non-monadic functions, as long as they are endo-functions, for the simplicity? What do I miss?

Related topics are

Monad join function

Haskell Monad bind operator confusion

Difference in capability between fmap and bind?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 2
    Since the equivalence between these two forms is already clear to you, try rewriting some example code using `>>=` and `return` to use `fmap` and `join` instead. Is the result clearer? Does it use less "monadic functions"? If so, then include such a sample in your question, to make it clearer what you wish could happen; if not, then you have your answer as to why it is not often done. Also see https://stackoverflow.com/q/35387237/625403, – amalloy Jul 17 '18 at 08:26
  • Actually, I did, in JavaScript. It is clear to me that to make the code work in monadic way without monadic functions, all we have to do is create the composed bind with fmap*joint. The reason I don't put sample is I don't want make this topic limited to JavaScript and some concrete example. –  Jul 17 '18 at 08:39
  • 1
    I don't understand your point. Whay do you mean with "a monadic function `a -> m a` is not required? With only `fmap,join,>>=` you can not define `return`. Indeed, `return` is the only primitive that lets us create a monadic value from a non-monadic value. If you prefer, I think you could replace `return :: a -> m a` with `base :: m ()` satisfying a bunch of laws, and then have `return x = fmap (const x) base`. – chi Jul 17 '18 at 08:40
  • 1
    I believe the types should be `return :: a -> m a` (not `unit`), `fmap :: (a -> b) -> m a -> m b`, and `(>>=) :: m a -> (a -> m b) -> m b`. (With the requirement `Monad m`.) If you get the types wrong, none of it makes any sense. – molbdnilo Jul 17 '18 at 08:41
  • @chi , sorry it is obviously a typo and I modified it. It's `a -> m b` the nomadic function, not unit/return `a -> m a`. –  Jul 17 '18 at 08:45
  • @molbdnilo same, it was a typo. I didn't mean that of course. –  Jul 17 '18 at 08:47
  • short answer: your quoted text itself talks about `return` which is a "monadic function" in your terminology. So no, you can't do without it. also, `a -> b` is *not* the type of endofunctions (that would be `a -> a` only). – Will Ness Jul 17 '18 at 10:13
  • 1
    What is the type of `readIORef` in your proposed alternate world? Is it an `a -> m b` function or an `a -> b` function? If an `a -> m b` function, is this suitable motivation for having `a -> m b` functions (it certainly is for me)? If not, why not? – Daniel Wagner Jul 17 '18 at 15:35
  • @WillNess I think your answer is irrelevant because you merely could mention terminology issue. I think I made the things clear showing mathematical structure. Please do not make the topic confusing with playing words. –  Jul 17 '18 at 20:00
  • @bayesian-study I think there’s is a misunderstanding here related to the nature of Haskell’s polymorphism and that it is causing miscommunication. I think Daniel Wagner’s last comment could help clarify this. – David Young Jul 17 '18 at 21:07
  • repeat quote, *"it is also possible to define a monad in terms of `return` and two other operations, `join` and `fmap`"*. IOW *three* functions are needed, not two, specifically the `return :: (Monad m) => a -> m a`. this is also mentioned in the answer below which is from one of the leading scientists in the field. for any function `f :: a -> b` we have `(return . f) :: (Monad m) => a -> m b`. moreover, opaque types like `IO` which the user doesn't have access to the definition of, inevitably must provide some primitives with such signature as well, like e.g. `putStr :: String -> IO ()`. – Will Ness Jul 18 '18 at 07:47
  • 1
    What do you mean by, _"a monadic function `a -> m b` is not required and normal functions `a -> b` will satisfy the operation"_? You also said that, _"It is clear to me that to make the code work in monadic way without monadic functions, all we have to do is create the composed bind with fmap*joint. The reason I don't put sample is I don't want make this topic limited to JavaScript and some concrete example."_ Why don't you share the code example? It would be really helpful for us to understand what you're trying to convey. – Aadit M Shah Jul 31 '18 at 12:30
  • @AaditMShah Hi, I'm really glad you've joined this topic, and eventually, I think you are the one to deserve the 500point bounty contribution. I will edit my Question, and let you know when it's done. Currently, not enough time to write, so. Just greeting. –  Jul 31 '18 at 13:24
  • You should accept one of the two answers provided and @KenOKABE should award them the bounty because both of them are absolutely correct. – Aadit M Shah Aug 04 '18 at 07:29
  • @AaditMShah No, pigworker understand the problem and he answered in that context, however, DarthFennec doesn't know the real Monad concept and looking at you supporting the answer as absolute correctness neither do you. –  Aug 04 '18 at 08:30
  • I answered by myself and it's so disappointing to see very few knows what's the real problem is. –  Aug 04 '18 at 08:31
  • @AaditMShah and just let you know, I downvote you guys simply because your understanding is not enough, and spreading a wrong idea. If you downvote my answer, just clarify reason, and you are welcome to discuss your justification of conduct. –  Aug 04 '18 at 09:44

4 Answers4

10

In a sense, you're right. As every monad m is a functor, we can use fmap f with a function f :: a -> b to turn an m a into an m b, but there's a catch. What's b?

I like to think of such an m as meaning "plan-to-get", where "plans" involve some sort of additional interaction beyond pure computation. If you have a "plan-to-get Int" and you want a "plan-to-get String", you can use fmap with a function in Int -> String, but the type of that function tells you that getting the String from the Int involves no further interaction.

That isn't always so: perhaps the Int is a student registration number and the String is their name, so the plan to convert from one to the other needs an external lookup in some table. Then I don't have a pure function from Int to String, but rather a pure function from Int to "plan-to-get String". If I fmap that across my "plan-to-get Int", that's fine, but I end up with "plan-to-get (plan-to-get String)" and I need to join the outer and inner plans.

The general situation is that we have enough information to compute the plan to get more. That's what a -> m b models. In particular, we have return :: a -> m a, which turns the information we have into the plan that gives us exactly that information by taking no further action, and we have (>=>) :: (a -> m b) -> (b -> m c) -> (a -> m c) which composes two such things. We also have that (>=>) is associative and absorbs return on left and right, much the way ; is associative and absorbs skip in classic imperative programming.

It's more convenient to build larger plans from smaller ones using this compositional approach, keeping the number of "plan-to-get" layers a consistent one. Otherwise, you need to build up an n-layer plan with fmap, then do the right number of joins on the outside (which will be a brittle property of the plan).

Now, as Haskell is a language with a concept of "free variable" and "scope", the a in

(>=>) :: (a -> m b) -> (b -> m c) -> (a -> m c)

representing the "overall input information" can just be taken to come from the scope of things we already have, leaving

(>>=) ::       m b  -> (b -> m c) ->       m c

and we get back "bind", which is the tool that presents the compositional structure in the most programmer-friendly form, resembling a local definition.

To sum up, you can work with a -> b, but often you need b to be "plan-to-get something", and that's the helpful thing to choose if you want to build plans compositionally.

pigworker
  • 43,025
  • 18
  • 121
  • 214
  • Thanks for your thought. How about an issue of compatibility? To me, it's obvious there are many endo-functions type libraries, for instance Math libraries are endo-functions. If we simply can use `a -> b`, we don't use much brain to take advantage of them to monadic context, but in the situation `a -> m b` is required, "oops, I forgot make to convert that and that" confusion must arise. –  Jul 17 '18 at 08:56
  • @bayesian-study In math, it common for functions to be total, thus you see a lot of `a -> b` function signatures. `a -> m b` arises when there a computations which have other effects. For example, if we model division, we have an edge case where we might fail when the denominator is 0. One way to model this is with a `Maybe` type: `divide :: a -> Maybe b`. – Yuval Itzchakov Jul 17 '18 at 09:08
  • 1
    Given that `a -> m c` is a special case of `a -> b`, a lot of that material is highly reusable. Moreover, if you have a pure `f :: a -> b`, then `(return . f) :: a -> m b` is the extra bit of plumbing you need. What you don't get to do is give `Int -> m String` things the type `Int -> String`, because that would be a serious lie. So yes, the setup does require a bit of extra plumbing to put the extra `m`s where they're needed, and it does cause some confusion. Some people (e.g., me) advocate a cleaner separation of the notions of value and effect. NB I didn't pay to be asked this question! – pigworker Jul 17 '18 at 09:11
  • 1
    @pigworker Interesting perceptive, however, the type thing you insist is also tricky and to be carefully, because if your mind set is `Int -> m String`, why don't you define a special type `Int String`? then it becomes endo `Int String a -> Int String b`. I still don't see much justifications we must stick to `a -> m b` because, as you say `a -> m c` is a special case of `a -> b`, why not the matter generalized?? –  Jul 17 '18 at 09:21
  • 1
    @Yuval Itzchakov I can't agree to your opinion that "something will fail" by using more generalized form `a -> b` not the special form `a -> m b`. I would agree if you mention the opposite, obviously. –  Jul 17 '18 at 09:24
  • @bayesian-study How would you represent a division by 0 with the signature `a -> b`? Can I always compute `b` for any `a`? – Yuval Itzchakov Jul 17 '18 at 09:26
  • 1
    @Yuval Itzchakov Simply I define a type of `mb`. and the endofuction is `mb a -> mb b` That is what we want right? –  Jul 17 '18 at 09:27
  • 1
    Well, I'm basically a heavy JavaScript guy from duck-typing world, and not bothered by static-typing around. I simply feel something wrong to limit the type behavior in advance of writing code. You guys are quite right if you strongly feel the type`m` gives you a "free hand", your mind set is duck-typing not static. Welcome to duck-typing world. It's definitely not a problem of generalized `a -> b` endo-functions! –  Jul 17 '18 at 09:36
  • @bayesian-study Not sure I follow your train of thought. What is `mb` to you in this context? Can you give a concrete example? How would you model the example I provided with division? – Yuval Itzchakov Jul 17 '18 at 09:48
  • `mb` would model a `Maybe a` in this case? What value would represent the absence of a result in the codomain? – Yuval Itzchakov Jul 17 '18 at 09:51
  • 3
    What would this type `Int String` mean? It couldn't be called `Int String` because `Int` and `String` are already types. And why would a special type be a good idea if the point is to achieve *appropriate* generality? Suppose I want to convert back from a name to a registration number? That'll need a `String -> m Int` for *the same* `m` but different value types. The point is to indicate what sorts of *interaction plans* are allowed, independently of the types of value to be computed by means of those plans, and allowing plans to be constructed compositionally. – pigworker Jul 17 '18 at 11:28
  • The comments are deleted for some reason, so I must re-post an important fact. `Maybe a` has absolutely no relation with `a -> m b`. There is "Maybe Monoids". –  Jul 17 '18 at 20:17
  • @YuvalItzchakov I also should have mentioned the fact there is `fmap` of Maybe "Maybe Functor" http://blog.ploeh.dk/2018/03/26/the-maybe-functor/ –  Jul 17 '18 at 20:30
  • @pigworker I'm from duck-typing world, so please excuse me to explain the duck-typing oriented idea in JavaScript. It is simply as follows: –  Jul 17 '18 at 21:02
  • In JS, `Number('123') =123`, also `Number(Number) = Number` –  Jul 17 '18 at 21:09
  • Obviously, this corresponds to `unit :: a -> m a` and `join :: m (m a) -> m a` –  Jul 17 '18 at 21:11
  • If you add `fmap :: (a -> b) -> (m a -> m b)` method to `Number` object/function prototype, the pair of object ad method of `Number` becomes a monad. –  Jul 17 '18 at 21:15
  • In this Monad scheme, I can't see any justification to give an extra space for the fancy `a -> mb`. –  Jul 17 '18 at 21:18
  • In a sense, Number type is a natural born Monad, `1 = (1) = ((1))` because it embraces the built-in structure of `join` or flatten, no need to give foregin natures later. You can chain `map` of `fmap` to the numbers. Nobody don't `bind` between numbers. –  Jul 17 '18 at 21:25
  • So, the whole point is "how to obtain a type of fractal structure line Number, from any other types?" right? Moggi or Philip Wadler chosed Kleisli-category and `bind` `a - mb` which is fine, but I simply feel wrong to stick to it. It's just a historical reason. –  Jul 17 '18 at 21:34
  • 2
    @KenOKABE I am confused. Are you reposting your own comments that got deleted or someone else’s comments? Also, there are a lot of things to unpack and discuss from those comments. I believe there are at least one or two misunderstandings that are obscuring things. – David Young Jul 17 '18 at 21:40
  • @DavidYoung No, I have repost an important reference deleted along with some off-topic controversy, and please do not repeat to transform the topic to communication staff. If you insist, just email me kenokabe@gmail.com –  Jul 17 '18 at 21:50
  • 1
    @KenOKABE I’m afraid I don’t understand what you mean, but I hope this discussion ends up working out well for everyone. Maybe the SO chat would be a better venue for it though? – David Young Jul 17 '18 at 22:04
  • @pigworker would this *"cleaner separation of the notions of value and effect"* mean replacing the EDSLs (which the monads are), with some kind of syntax-based DSL? what would replace `putStr`, for instance? – Will Ness Jul 18 '18 at 08:23
  • @WillNess Why ask "do you mean X?" when you could just ask what I mean? Alternatively, you could read my POPL 2017 paper, ungooglably named "Do Be Do Be Do", where I say what I mean. – pigworker Jul 31 '18 at 01:58
8

I'm having a bit of a hard time understanding what your question actually is, but I'll take a crack at it anyway.

I think that since >>= can be composed of fmap and join, a monadic function a -> m b is not required and normal functions a -> b will satisfy the operation,

I expect you're referring to the "monadic function a -> m b" in the type for >>=, is that correct? Well, let's see what happens when we replace that with a function of type a -> b:

(>>=) :: m a -> (a -> m b) -> m b  -- standard version
(>>=) :: m a -> (a -> b) -> m b    -- your version

But doesn't this look familiar? It's equivalent to fmap :: (a -> b) -> m a -> m b, but with the parameters switched. In fact, the implementation is just x >>= y = fmap y x, no need for join. So there's your answer: if you use a "normal function a -> b" instead of a "monadic function a -> m b", you no longer have a monad. Instead, you have a Functor.


but so many tutorials around the web still insist to use a monadic functions since that is the Kleisli triple and the monad-laws.

I'm not sure which tutorials you're looking at. In my experience, the nature of tutorials is that they insist on whatever they're a tutorial for. It would be weird if a tutorial for Monads started presenting problems, and then suggesting things other than Monads as solutions; at the very least, that would be out of the tutorial's scope, and a waste of time for anyone reading it to learn about Monads.


Well, shouldn't we just use non-monadic functions, as long as they are endo-functions, for the simplicity? What do I miss?

Endofunctions are functions of type a -> a. Given the context of your question, I think you actually mean pure functions of type a -> b ("pure" as opposed to inherently monadic functions such as readIORef that need to be type a -> m b). If my assumption is wrong, let me know, and I'll edit the question.

EDIT:
As suggested in a comment by @duplode, it's likely that you mean endofunctor, which in Haskell is just any type of Functor. In this case, the below still applies.

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.

DarthFennec
  • 2,650
  • 17
  • 24
  • I feel the OP might be mixing up "endofunctions" with "endofunctors" (as in "a `Functor` is a **Hask** endofunctor"), but that is just a guess. – duplode Aug 01 '18 at 03:54
  • @duplode This is very probable. I've edited the answer to include this. – DarthFennec Aug 01 '18 at 16:49
  • and just let you know, I downvote you guys simply because your understanding is not enough, and spreading a wrong idea. If you downvote my answer, just clarify reason, and you are welcome to discuss your justification of conduct. –  Aug 04 '18 at 09:44
  • 2
    @user6440264 The reason I'm having a hard time understanding your question is because I can take it about ten wildly varying ways that all make equal amounts of sense. Put another way, the reason my "understanding is not enough" is because *you aren't being clear enough*. I don't think I deserve downvotes for that. I'd have liked for us all to work together and learn from each other (as is ultimately the purpose of this site), and I'm a little disappointed that that hasn't turned out to be possible. – DarthFennec Aug 05 '18 at 16:16
  • @DarthFennec https://stackoverflow.com/posts/51376391/revisions ; https://imgur.com/a/SyzZbFw – Will Ness Aug 07 '18 at 13:24
  • @WillNess Why link me the revision history? I'm not sure what I'm looking for. – DarthFennec Aug 07 '18 at 16:43
  • just some meta stuff there, related to the votes you've received. you can safely disregard all that. :) – Will Ness Aug 07 '18 at 18:18
  • @WillNess Ah, gotcha. I wasn't worried about my votes, I was just disappointed in his behavior. Thanks though ^^ – DarthFennec Aug 07 '18 at 19:00
2

There is no reason.

I think that since >>= can be composed of fmap and join, a monadic function a -> m b is not required

Yes, you're totally right. We don't need to require >>= for a monad, we could also require join instead. The two are totally equivalent. As we can also compose join = (>>= id), we can do either

class Monad m where
    return :: a -> m a
    fmap :: (a -> b) -> m a -> m b
    (=<<) :: (a -> m b) -> m a -> m b
    -- join = (=<<) id

or

class Monad m where
    return :: a -> m a
    fmap :: (a -> b) -> m a -> m b
    join :: m (m a) -> m a
    -- (=<<) = (join .) . fmap

It doesn't matter which one we use. Admittedly, the latter looks simpler because there is only one higher-order function (fmap), in the former the types of fmap and =<< look too similar. join gives a better idea of what distinguishes a monad from a functor.

Versatility

We can derive >>= from fmap and join, but we can derive join from >>= only. In fact, we can even derive fmap from >>= and return. So should we say that >>= is more basic than the other? More powerful? Or maybe just: more convoluted?

We would rather like to write

data Maybe a = Just a | Nothing
implement Functor Maybe where
    fmap = (=<<) . (return .) -- maybe not even write this ourselves
implement Monad Maybe where
    return = Just
    f =<< Just x = f x
    _ =<< Nothing  = Nothing

than

data Maybe a = Just a | Nothing
implement Functor Maybe where
    fmap f (Just x) = Just (f x)
    fmap f Nothing  = Nothing
implement Monad Maybe where
    return x = Just x
    join (Just (Just x)) = Just x
    join (Just Nothing)  = Nothing
    join Nothing         = Nothing

The former solution using >>= is minimal.

Convenience

Well, shouldn't we just use non-monadic functions for the simplicity?

No. The whole idea of defining the monad typeclass is to ease working with monadic functions. On their own, the return/fmap/join functions are pretty useless, what we are interested in are other functions that return the monadic data type: tryParse :: String -> Maybe Int for example.

And the whole idea behind monads is that we can arbitrarily chain and nest them, getting back a plain type in the end. After having parsed a number, we need to validate the optional result (giving us another optional result) - in the monad (fmap validate) before getting it back out. There are usually no operations that yield nested data directly, we only get nested monad types because we do further monadic operations inside a monadic type. And we'd much rather write

tryRead = (=<<) validate . tryParse

than

tryRead = join . fmap validate . tryParse

That's why >>= is more important for using monads in daily life than join. I would also guess that having to implement >>= directly, rather than implement join explicitly and have >>= get derived from it, allows for better (easier) compiler optimisations.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • what good is `join` without any function of `Monad m => a -> m b` type? It'd be an applicative then, not a monad. `join [[x+1, x*2] | x <-[10..13]]` is just `pure (&) <*> [10..13] <*> [(+1),(*2)]`. Monad is a *Kleisli* triple, by definition, isn't it? What would replace `print`, for example? Some verbs of a non-pure Monad just have to be supplied as primitives. Not everything is achievable by composition with `pure`, unless that monad itself is *pure*, of course. – Will Ness Aug 07 '18 at 10:59
  • @WillNess Yes, that's exactly what I meant by "*On their own, the `return`/`fmap`/`join` functions are pretty useless*" – Bergi Aug 07 '18 at 12:05
  • all I saw was, "a monadic function `a -> m b` is not required" --- "Yes, you're totally right." – Will Ness Aug 07 '18 at 12:23
  • @WillNess Oh, right. I understood that as "a monad method `>>=` that takes a monadic function is not required". – Bergi Aug 07 '18 at 13:00
0

Composition of Kleisli arrows a -> [b]

 (a->mb) -> (b->mc) -> (a->mc)
    f          g  

a, b, c are arbitrary types, m is always the same, here it is []

We have to produce a function (a->mc)
Functions are produced with a lambda, we have one argument a.

f >=> g =  \a -> ..f..g..
           \a -> let mb = f a;
                 in mc = mb >>= g

Above is taken from Bartosz Milewski Category Theory 10.1: Monads

mb >>= g producing mc    That looks like a functor

mb >>= b->mc     variable substitution: mc -> c'
mb >>= b->c'     now this is a functor!
fmap b->c' mb     fmap goes under the hood
m (b->c' b)      back substitution c' -> mc
m (b->mc b)
m (mc)    is not mc, so  another try!

join fmap g mb
join m(mc)     Monoid
mc       is mc   OK!

mc = mb >>= g
mc = join $ fmap g mb

g is b -> mc, so >>= and join-fmap use it the same way. If it's not available, you can build it with return.

return
If we have b->c instead of b->mc

mb >>= (b->c) -> (b->mc)
         f
mb >>= \b -> let c = f b ;
             mc = return c
             in  mc
return :: c -> mc

mc = mb >>= \b -> return $ f b
and
mc = join $ fmap (\b -> return $ f b) mb

f is b -> c, you can use it together with return instead of b -> mc (Your Question).