6 ms·
I've tried Haskell 4 times. No joke. I have no problem with currying, higher-order functions, foldl, etc., but Monads. Get. Me. Every. Time. My brain just refu
by PopsiclePete 11y ago
I've tried Haskell 4 times. No joke. I have no problem with currying, higher-order functions, foldl, etc., but Monads. Get. Me. Every. Time.
My brain just refuses to fully "grok" them. I still don't see why they're so awesome. I know I use them day-to-day - LINQ, the "List" abstraction (supposedly also a monad??) but I just don't see why it's important to understand them on this whole new fundamentally different level.
It's like - loops. I use loops every day. But if someone were to say - "Hey, did you know that loops are just the Andifuncsplursx abstraction applied on Crazofors?? This is why Crazofors in Haskell are so awesome" I still wouldn't "get" Crazofors or why I should care enough to attempt to "get" them.
I've just given up at this point.
- platz 11y agoWhat things/path did you attempt while trying to learn haskell? Also, i dont think you need to understand crazofors, ot even monads in full generality, unless youre a hardcore library implementor. What people blog about and whats req'd to write an app are very different things
- Verdex 11y agoI took me several years to grok monads. I even learned some category theory to try and get there (it didn't really help, but I do think it was useful for some other almost programming related things). The nice thing about monads with respect to the haskell programming thing is that you don't have to know why they work to get their benefit. However, if you wanted to build your own you probably do need to grok them. However, I'm not convinced that monads are the best way to proceed on the most part in programming advancements. The immutable/mutable tracking, affine types, and borrow checker in Rust seems like it would help prevent a lot of the same types of bugs that pure FP does, but without the cost of needing to have a monadic effect system (and Rust is a more familiar style of programming than the State monad with a host of monad transformers). I'm hoping for optional affine types and more immutable data structures to become the norm more than I am monad usage.
- chriswarbo 11y ago> I've just given up at this point. Given up trying to understand Monads, or given up Haskell? I'm still confused by many monads, after years of Haskell programming; it hasn't stopped me getting stuff done.
- louthy 11y agoThis [1] might help 'get it' with regards to monads. Very easy to digest. You mention LINQ and the List abstraction. Yes IEnumerable<T> is a monad (LINQ isn't in itself - it [the grammar] is the equivalent of 'do' notation in Haskell). Monads are simply 'wrapper types' that follow a couple of rules: 1. You must be able to construct one from the un-wrapped value (return in Haskell, new List<T>(...) in C#) 2. It must implement the bind function. If you understand 'map' (or Select in LINQ) then bind is very similar, except instead of returning a mapped version of the wrapped value, it returns a mapped version of the wrapped value, re-wrapped. So in C# parlance (because I assume from your comment you use this daily): IEnumerable<R> Map<T, R>(this IEnumerable<T> self, Func<T, R> mapper) { foreach(var x in self) { yield return mapper(x); } } IEnumerable<R> Bind<T, R>(this IEnumerable<T> self, Func<T, IEnumerable<R>> binder) { foreach(var x in self) { foreach(var y in binder(x)) { yield return y; } } } You may recognise that as SelectMany in LINQ. Select and SelectMany are special case function names in C# that allow the formation of LINQ expressions: var res = from x in list select x + 1; Equates to: var res = list.Select( x => x + 1); And: var res = from x in list1 from y in list2 select x + y; Equates to: var res = list1.SelectMany(x => list2.Select(y => x + y)); The result is a 'flattened' IEnumerable<T> - and that's why this is also known as flat-map. It's been a while since I've done any Haskell, so forgive me if I get some of the syntax wrong here. But the LINQ statement above translates very similarly: do x <- list1 y <- list2 return x + y (I know there's a list comprehension syntax in Haskell, but I assume this is still right, Haskellers?) The syntax is saying 'get the value out of its wrapper (the list) for me, so I can use it as-is (the add operation), then put the result back in a new wrapper'. So why do we do all of this? It's so you can write the functions once that behave on the wrapped types, whether they're integers, strings or any type. The monad concept allows you to create a 'chain' of behaviour that from the outside is opaque, but internally it's operating on the raw wrapped values, as-is; and that makes all of the existing functions available. This is one of the key benefits. A good analogy is to call them 'programmable semi-colons', because what makes each monad type what it is, is the behaviour of bind and return. So a list monad (as seen above) knows how to iterate the collection - so you don't have to write a for loop every time, an option monad knows when to not invoke the bind function if it's in a None state - so you don't have to test it yourself with an 'if' every time, the state monad knows how to propagate state changes - so you don't have to have extra context arguments on every function down the hierarchy, etc. They remove boilerplate and capture common patterns. That is the other key benefit. At its core is an abstract idea, and I think that's why it's sometimes quite hard to grasp; but it's just a design pattern. Forget the category theory side of it, you don't need to know any of that to use use them or write new ones. If you're more used to C# than Haskell, then you may want to check out my 'C# functional language extensions' library [2] that has C# implementations of the following monads: Option Either State Reader Writer And a few others, but that should be enough to get started. It may help you get your head around it in a language you're more familiar with? [1] http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html http://adit.io/posts/2013-04-17-functors,_applicatives,_and_... [2] https://github.com/louthy/language-ext https://github.com/louthy/language-ext
- lallysingh 11y agoI had to read Real World Haskell's chapter twice: http://book.realworldhaskell.org/read/monads.html http://book.realworldhaskell.org/read/monads.html to figure it out. It's essentially a hidden argument that's passed around between the environment (the stuff in "do ... ") and the actions (the stuff called in "do ...", e.g, "x <- foo", foo would be the action). The argument is always the same type, and the each action can create a new one based off the old one. So you have IO, which is essentially "all interactions with the outside non-pure world", and the versions of it are "the world before I did this action" and "the world after I did this action". Stuff that doesn't use the hidden arg doesn't have the action type (e.g., IO ()), so it has to be lifted, which essentially passes the old world state verbatim to the next step.
- thinkpad20 11y agoIn my opinion, the only way to understand monads is to use them. They'll be a little weird at first, but the type system will guarantee you're using them in the right way. The thing is that you don't really need to understand how a given monad works under the hood; you just need to know how it's going to act when you use it. For example, I couldn't write the State monad instance right now, not without quite a bit of puzzling anyway. But I use the State monad all the time, because I know how it's going to act: addToState :: Int -> State Int () addToState number = do currentState <- get put $ number + currentState How is this working exactly? Well who cares. I know that once this function is called, my state will have been incremented by the given amount.
- codygman 11y ago> How is this working exactly? Well who cares. I know that once this function is called, my state will have been incremented by the given amount. Precisely. Then once you've used Haskell in many projects and gotten a handle on using it, you can figure out the relationships/laws and use it to even greater effect.
- tome 11y ago> My brain just refuses to fully "grok" them. I still don't see why they're so awesome. I know I use them day-to-day - LINQ, the "List" abstraction (supposedly also a monad??) but I just don't see why it's important to understand them on this whole new fundamentally different level. Why do you want to "grok" them? Just use them. In fact I'd say there isn't much more to grokking them than just using them.
- dandrews 11y ago"Why do you want to 'grok' them? Just use them." You remind me of my first ground school instructor. I'd asked her why it was necessary to use rudder in a turn, and she waved her hand dismissively. "Just step on the ball" she recited, referring to the turn-and-bank indicator. No thank you, I'd rather know what's keeping my plane stable, so I found the answer elsewhere: Langewiesche's awesome Stick and Rudder, still relevant 70 years after publication.
- codygman 11y agoThat's a little different I think. What are the potential repercussions of not intimately understanding monads and the monad laws in Haskell? Much lesser than not understanding ruddering into a turn I'd guess, though I don't know what it is. What do you think?
- dandrews 11y agoSome pilots are just drivers - I'd certainly rather ride with the former, given the choice! While it's tempting to characterize some programmers as just coders, that sounds pejorative and wouldn't be very charitable of me. So instead I'll distinguish programmers as tool makers and tool users. I know which one of those I'd put my faith in too, all things equal. No offense intended... at all! I don't grok monads either, but in my world (mainframe stuff) Haskell doesn't register. If I used monads though I'd surely be driven to understand what's going on under the hood. Forgive me another tangential OT story, an anecdote I read in a magazine many years ago. A man was spending a Saturday afternoon puttering around in his back yard while the family's hound slept on the back porch. The man called the dog, who then roused and put his nose to the ground, retracing all the steps his master had taken that afternoon until he finally reached the man. "The dog didn't give a damn about coming to me" the man growled, "He just wanted to know how I got here." Hee! That's me.
- CuriousSkeptic 11y agoI guess the problem is the focus on the rules, mechanics or syntax. It's like trying to understand roundness by having some explain formulas involving Pi. Just forget the math, look at all the squares, triangles and circles. When you start to notice the sameness between ovals and circles and how they aren't squares you start to get the notion. When it comes to monads, it's just the mechanics behind why some things, are intuitively composable. You don't need the mechanics if you just get a feeling for it. In linq this is the feeling that you can stack a bunch of from x in xs from y in ys from ... together and get sensible results without too much thinking. As long as the xs and ys implement SelectMany as a monad it doesn't matter what it does, be it transforming collections, building sql queries or scheduling async tasks, the safe feeling will be there. If you really want to do the math it might help to identify the level of abstraction we are talking about. For this I found it easier to reach for abstract algebra. Lookup the concept of a monoid and how it relates to you everyday arithmetic, it's roughly the same abstraction leap as the difference between your code and the concept of monads. Now, monads really are just monoids for a particular kind of binary operation and values. The problem is that understanding monoids in the context of multiplication and addition is easy, you already have good grasp of both the arithmetic and the algebra there. But the compositions that monads describe (kleisly arrows) are probably not something you think about most of the time. Which kind of is like trying to understand abstract algebra without a good grasp on algebra.
- codygman 11y agoStop trying to get them and use some specific monads. Monads are nothing but something that satisfies 3 laws. This is what helped me most.
- auvrw 11y agomonads are not unique to Haskell. read the source for webdriver.io and you'll see that monads exist in js, too. at that point, you might say "but this thing they're calling a monad in js is just an object.". well, these things we call monads in Haskell are just type classes. now, a more fundamental question is "why do we care about types so much," and I don't have a pat answer for that one. suggestions to try out lisps for comparison makes sense, tho.
- bonobo3000 11y agoHeres what finally got me to understand monads, including some of the context around them: 1. Why is it important that a List is a monad? A. Its not particularly important. Its really just pointing out that monad is a very general abstraction - it wont tell you anything you don't already know about Lists. On the other side, lists as kind of trivial examples of monads - they didnt really help me understand monads either. Its like saying 1 is a real number - true, but it wont help you understand real numbers. 2. Why should i use monads? A: I like to think of monads as things you can use in a for-comprehension (or the do notation in Haskell). If you can imagine writing something like for {x <- thing; y<-thing} x + y for "thing", then "thing" might be a monad (assuming all the math laws work out - sometimes they don't). In scala, for-comprehensions are literally de-sugared into maps/flatMaps/filters so for comprehension without filter <=> monad. 3. But a monad is just a monoid in the category of endofunctors? A: There is deep category theory and math behind this stuff. It can be useful to talk about it, but when you are starting out its overkill. Don't worry about "getting" the really abstract crap at first, just skim right over. Programming in monads is a lot easier than the theory, and the theory can be learned after getting your hands dirty. Using monads is mostly just for-comprehensions. ps: "monoid" = there is a zero, and an add operation. like integers with +, or integers with *). "functor" = thing that has a map() operation. "endo" = self "category" = kind of like a set. its a container. So a monad has a "zero" or default monad, a way to add monads, and a map operation that returns another monad. e.g List -> zero = Nil, add = append, map = the "normal" map with f applied to each element Future -> zero = empty Future, add = do future2 after future1, map = make a future with the f applied to value
- meowface 11y agoWhat helped me grok monads was to understand that monads are just a subset of functors. Then I did what I could to fully understand what functors were, since at that point they were still a kind of cargo-cult black box for me. let liftF = fmap liftF negate (Just 1) liftA negate (Just 1) liftM negate (Just 1) Realizing these all performed the same operation (if operating on a monadic value), and that `fmap` is just a `liftF` ("lift a regular function to work on a functor"), cleared some things up for me. This also helped: https://en.wikibooks.org/wiki/Haskell/Applicative_functors#A_sliding_scale_of_power https://en.wikibooks.org/wiki/Haskell/Applicative_functors#A... So all these abstractions really just let you do different things with functions that normally don't take functor arguments. They make the function basically allow functor arguments (kind of), to save writing a lot of boilerplate within your functions; typically boilerplate that would be put at the start of every function. Once someone really gets the idea of what a functor is and why they can be useful, I think the rest is easy to understand once you read the definition of monads and applicatives.
- mbrock 11y ago"Monad" is an abstraction. What kind of abstraction? An algebraic abstraction. That means really understanding the concept is much like understanding other algebraic abstractions. The most basic concept in classic 19th century abstract algebra is "group." Just like the monad concept, this concept involves a set of values that can be combined with a few carefully chosen operations. Just like with the monad concept, the group concept doesn't lend itself to immediate grokking. So a lot of people get frustrated by abstract algebra. They feel like someone just isn't telling them what a group "actually is." But group is an abstraction over concrete "implementations". It is a common base for many algebraic topics, like integer arithmetic, modular arithmetic, matrix arithmetic, polynomial arithmetic, and even more complex structures. If you are familiar with the theory that applies to groups in general, you have access to proofs and formulations that can be applied to many different topics. Sometimes that generality is useless, sometimes it is very productive and succinct. What the groups have in common are a binary operator that's associative (like plus or times) an identity element (like zero or one) and a way of taking inverses. This is all codified as group axioms. If you just look at those axioms you might say "so what?" but the concept is born from actual mathematical practice and is significantly useful and interesting. Monad is an abstraction over different computational topics: I/O computations, randomized computations, failing computations, and so on. It captures in an elegant and abstract way the operations and elements required to express these topics. General functions can be written polymorphically over all monads, just like theorems and computations can be written to work for all groups. So for a monad, you need return, which lifts a base element into the monadic class of values. (This notion of having a base element and a lifted set, for example Int and Maybe Int, is itself a basic abstraction that monad builds on, namely the functor abstraction, whose only operation is fmap, an abstraction of list mapping.) And you need bind, which is some way of combining one monadic value with a function producing another monadic value. Those operations need to work together in reasonable ways specified formally by the monad axioms or monad laws. Again, you can look at all that definition stuff and say "So what?" But again, the concept makes sense, it is useful, and it is born from abstracting over concrete topics. (Moggi wrote the first paper about the usefulness of the monad concept in computer science; the concept originally came from category theory, which is kind of like abstract algebra.) The do notation is a good example of the usefulness of having an abstract type class for monads. It gives you syntactic sugar that works in a well defined way across many many topics. Abstract algebra doesn't make any sense if you don't know how to work with plus and minus. Monads don't make sense if you're not comfortable with implementing pure combinators for simple computational structures. So you should find some way to practice some of those basics, and then the abstraction "monad" will have meaning and not just look like a random assemblage of made-up rules. Look at an example of using do notation with Maybe types to express failure. It's not that amazing, but it's useful enough, and makes sense. Now learn to implement the same thing from scratch without the syntactic sugar and without the monad functions. You will first write the whole thing with explicit pattern matching, tediously. Then you will implement the crucial combinators that lets you "bind" one Maybe value to another computation returning a new Maybe value. Then you can look at the source code for the Maybe instance of Monad and see that it is just the combinator you have written. Then you can study the State monad in a similar way. And then you can notice how the general monad functions are useful for both of those topics, failure and statefulness. The concept monad in the context of category theory is even more abstract, but there's no need to worry about that level of abstraction merely to learn Haskell programming. The reason monads are a big deal in Haskell is that the computational structures they conveniently express happen to be those which are otherwise described with "imperative" language features: mutation, jumps, and side effects. So if you're interested in expressing those computations in a pure way, you should be a bit curious about the monad concept. If you're not interested, that's fine, but it's close to Haskell's reason for existing, so that's a more basic question: is it interesting to write programs in a pure way? If you say no, you're right to give up on Haskell.
- jfoutz 11y agoIf you understand currying, you've got monads pretty much. They curry in a hidden thing.
- jfoutz 11y agoIf you understand currying, you've got monads pretty much. They curry in a hidden thing.
- vans 11y agoLike Erik Meijer said in his course : there is nothing special or magic about monads. They don't deserve all the fuzz arround them. If you don't get why monads are so awesome, maybe that's a proof that you understood them. Because there is nothing special !! A monad is just a type with 2 functions defined. Like an interface with two methods in OO langages. The 2 functions have to respect some laws but you can imagine whatever implementation for the 2 functions as long as the laws are observed (et type signature of course). You could invent a total different implementation for the list monad, for the maybe monad, etc (if you respect the laws). There is no hidden ultra powerfull meaning which implies only one implementation. The best paper on monads i ever read is an ascii art one, by Graham Hutton : http://www.cs.nott.ac.uk/~pszgmh/monads http://www.cs.nott.ac.uk/~pszgmh/monads
- kqr 11y ago> You could invent a total different implementation for the list monad, for the maybe monad, etc (if you respect the laws). Could you show such a different implementation for either list or maybe?
- jjaredsimpson 11y agonewtype HeadList a = HeadList { getHeadList :: [a] } instance Monad HeadList where return a = HeadList [a] m >>= f = HeadList $ fmap (head . getHeadList . f) (getHeadList m) This is a list instance that only keeps the head of function result, so it's basically just a map. But you could imagine putting any function that returns one result. min max avg normalize, etc
- ky3 11y ago-- const [] for a HeadList quux :: a -> HeadList a quux = HeadList . (const []) *Main> HeadList "hello... no wait" >>= quux HeadList {getHeadList = "*** Exception: Prelude.head: empty list Such breakage is verboten. This monad instance does not fly.
- paulddraper 11y ago