8 ms·
As someone who stopped following Haskell world few years ago, this was quite interesting read. I wonder what is the state of ecosystem (libraries) now: a few ye
by rednum 6y ago
As someone who stopped following Haskell world few years ago, this was quite interesting read. I wonder what is the state of ecosystem (libraries) now: a few years ago one of my friends complained that for most basic tasks there is some library but usually half-working and abandoned.
However: I think the point about monads is fundamentally misguided. Monad is an abstract concept and trying to explain it in non-technical way (as linked from the post) will always fail. It makes as much sense as explaining time signatures in non-musical terms, or a Banach space in non-mathematical terms. I've seen this sentiment recurring every now and then; it feels like some people really want to avoid formal definitions for the sake of it, even when formal definitions make things easier. When it comes to monads, I think you can't get any better than the semi-formal definition (typeclass with return and join) plus some motivating examples. For me looking at Reader/Writer/State helped get this; I agree with OP that List is kinda weird.
- SeanDav 6y agoYou seem to be disagreeing with the OP about how to explain Monads, yet the link provided by the OP is referred to as a "non-explanation". It is true that Monads are a pain point in Haskell. They are extremely powerful but not easy to grok, even by some very smart programmers. Quote from main article about Monads: > "Monads.....Despite many attempts though, I don’t think we have yet found the best way of explaining what they actually are. " and > " The Wikipedia article has quite a good example of non-explanation."
- rednum 6y agoTo rephrase what I mean: I disagree with the idea that we can explain all technical concepts in non-technical way, which OP seems to support. I don't think we can get much better with teaching monads than providing actual formal definition plus motivating examples. I simply believe monad is too abstract and we can't have a nice analogy similar to the one OP provides ("Functors can be broadly explained in terms of containers..."). In my opinion the "platonic ideal of a monad tutorial" is more or less the (semi) formal definition of monad + some motivating examples. This is also something that cannot be understood if you don't have proper background (which is understanding simpler typeclasses like monoid and functor). I also would not call monads a pain point to be fair.
- mumblemumble 6y agoI would turn that on its head: Provide several motivating examples, together with concrete solutions. Once you've provided a couple of those, then you can call attention to the commonalities that unite all those solutions. At which point, the abstract explanation is almost trivial. For example: My understanding of monads ultimately came together rather quickly and painlessly. After I had initially just given up on the whole thing. And then I watched Scott Wlaschin's Railway Oriented Programming talk (https://fsharpforfunandprofit.com/rop/ https://fsharpforfunandprofit.com/rop/). And then I learned what's really going on with LINQ's query DSL and SelectMany. And had someone show me how to easily compose Optional types without having to forever be explicitly interrogating whether they have a value or not. With all that under my belt, the pattern suddenly became rather obvious. What did not help me in any way was any article that explicitly sets out to try and explain monads. They are all, as far as I'm aware, guilty of tring to sow the seeds before tilling the earth.
- lkuty 6y agoIt was quite similar for me. Scott Wlaschin's Railway Oriented Programming and its other articles on its site (and in particular https://fsharpforfunandprofit.com/series/map-and-bind-and-apply-oh-my.html https://fsharpforfunandprofit.com/series/map-and-bind-and-ap...) helped me greatly too. I liked its idea of an "elevated world". Another helpful source was/is https://wiki.haskell.org/All_About_Monads https://wiki.haskell.org/All_About_Monads. But I thought a lot about them for the past few years. I tried also to make a detour through category theory but it did not help except for getting a lot of books in my library. Another helpful article was Philip Wadler's "Monads for Functional Programming" (http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/ba...).
- jackcviers3 6y agoA monad is an abstract interface for sequencing side effecting operations in a way that guarantees referential transparency. They do this by hiding the extraction of the value contained in the monad and passing that value to a user supplied lambda that takes a value of the type of the value contained in the monad and returning a new monad containing a different type. Thus, you cannot do step one after step two, because step one must extract the value from its containing monad. In a modern OOP language, like Java, this is analogous to using for each in to build a new list from a given input list. Due to the abstraction, a monad can do this without exposing the internal loop state to the outside world, a single bind expression safe to refactor, whereas moving the for loop requires moving two statements in order to be safe to move.
- gavinray 6y agoWhat is "referential transparency"? > "by hiding the extraction of the value contained in the monad and passing that value to a user supplied lambda that takes a value of the type of the value contained in the monad and returning a new monad containing a different type" I'm not sure I follow. Is this like saying I have a function: let myFunc = (arg1: SomeType): NewType => {} And it takes SomeType, and returns NewType? What does it mean to "hide the extraction of the value"?
- gmfawcett 6y agoI wrote a Reddit post a while back that might help. (The parent comment has since been deleted -- the poster was asking whether using monads in Haskell was kind of like "dependency injection", i.e., passing in all the information that a function might need, as arguments to the function.) https://www.reddit.com/r/programming/comments/593ud7/a_taste_of_haskell/d962888/ https://www.reddit.com/r/programming/comments/593ud7/a_taste...
- louthy 6y ago> I'm not sure I follow Imagine you have a context (the monad), which contains a value of type `a`... > "by hiding the extraction of the value contained in the monad... Means, you don't need to care about how we get the value out of the context, it will just be done for you. That's part of the monad's job. Each different type of monad may do it differently (a List will do it many times, an Option will do it zero or one times, Async promises to give you a value, etc. ). > "... and passing that value to a user supplied lambda that takes a value of the type of the value contained in the monad ..." So, we have a function called `bind`, which is part of the interface for monads ... it takes the original context (monad) as well as a lambda as arguments. Below is the signature: `m a` which is the original monad value, (a → m b) is the lambda, and `m b` is the return value, which is a monad with its contextual value type changed from `a` to `b` bind :: m a → (a → m b) → m b Where you see `m a` and `m b`, they're generic types which are parameterisable on both the inner and outer type, the inner type `a` is fairly usual, but the outer-type `m` is a higher-kind and can also be parameterised, `m a` could become `List a` (which is like List<A> in C# or Java). So, if you squint, you might be able to see that the `a` comes from within the `m a` context, gets passed to the lambda, which needs an `a` argument and returns an `m b`, then that `m b` is returned as the final result. Each type of monad has its own implementation of `bind`. So, for a List the bind operation looks like this: bind :: List a → (a → List b) → List b And so you should see that for Lists each `a` will be extracted from the first argument (so we'll have zero or many `a` values), each `a` is passed to the lambda which will return a `List b` (and so we have zero or many `List b` values), and those lists of `b` will be concatenated to give a flattened result of `List b`. And so, that's the behaviour if the List monad. Each monad will have its own rules, but must follow the signature of `bind`. > "...and returning a new monad containing a different type..." This just means the `a` turns to a `b` (but the `m` stays the same). Essentially it means you can do some kind of mapping of the contextual value(s) to return a new type. So, if you were to map Ints to Strings the bind signature would look like so: bind :: List Int → (Int → List String) → List String In practice you can see how the lambdas returning `m b` allow for chaining of computations. The code below visits the items in two lists (listA and listB) and sums them. The lambdas close over each other to create a context for the `return` at the end of the process. Which is very close to how scoping works in imperative languages. bind listA (\a → bind listB (\b → return (a + b))) The key to any monad is its bind function, that's where its behaviour is. But fundamentally, it's a contract, or interface which forces ordering of computation... the values returned by the lambda (which are monads themselves), clearly can't be evaluated until you evaluate the first argument to get the `a`. And so, `m a` must run before `m b` ... combine that with `do` notation and you'll get something that looks and feels a lot like imperative code, but all of the side-effects are encapsulated and declared, and all of the logic is pure (referentially transparent). The example above with `do` notation is: a ← listA b ← listB return (a + b) Which is essentially equivalent to a nested for loop without the ceremony.
- danielscrubs 6y agoA sidenote: how great would it be if someday an abounded, well written, non buggy piece of code could just stand the test of time like math does? Instead we throw projects and programming languages faster than I change my wardrobe. :)
- abjKT26nO8 6y agoThe maths of today has very little to do with the maths of 500 years ago, let alone 2500 years ago. Sure, we still have the Pythagorean theorem, but in today's modern formulation it's trivial. We still have numbers, but they are no longer something that can be totally ordered. With numbers we even turned things upside down, because we used to first try to define single numbers and only then collections of numbers. These days we say that "a number is an element of a number field", "a vector is an element of a vector space" etc. and we define the structures first. But the biggest departure probably is the widespread reliance on proof. In XVI century you could call yourself a mathematician without proving things, as long as the ideas you came up with worked in practice. I'm sure that in the programming world we'll still have "if"s and variables/aliases 200 years from now. And that's right about how much commonalities modern mathematics has with the mathematics of the age of Euclid. Disclaimer: I took a course in history of mathematics and I have to say, if I was born as little as 200 years earlier I wouldn't be able to stand mathematics. These days, it's actually interesting to me. Similarly to how mathematics in primary through high school is really boring.
- danielscrubs 6y agoI mean conditions exist in math already and I'd argue that there is a bigger chance qwerty is still the default keyboard layout than your Python 3 program working in 500 years. How many of the popular languages even have formal specifications to use as a reference when the next CPU architecture comes along, or the next ABI? The tools exist but I don't see people rushing to Modula-2 for example, which has. It does remind me a bit of this paper "Boosting Vector Calculus with the Graphical Notation" [1], but we instead have over ten thousand only partially defined ones. But I can agree that my view of math is a bit too rosy. [1]: https://arxiv.org/abs/1911.00892 https://arxiv.org/abs/1911.00892
- hopia 6y agoMy experience is that the most basic tasks have a very mature library base in my context (web domain). Such as typed web endpoints, web servers and clients, JSON parsing and working with databases. I expect some domains are even far more mature with Haskell. However, when you explore more specific things like shipping emails via a 3rd party service, various authentication protocols, monitoring via external services, spinning up persistent job queues etc. you start running into odd problems that you need to debug and even possibly fix at library level. That may considerably slow down development.
- ashtonkem 6y agoI fully grok monads. I’ve done enough reading and usage of them to understand them. They are not a useful abstraction for programming. They’re mathematically correct, but that’s not the same as what an industrial engineer needs. The big warning sign is monad transformers. Alone, monads are totally fine, but the issue is that you rarely want one. So you end up with this unwieldy tower of transformers that would make an enterprise Java engineer worried.
- johnday 6y agoI firmly believe that monads (and monad transformers) are exactly what an industrial engineer needs, for the following important reason. A monad describes its scope in such a way that writing code outside of its scope is a compile time error. If your code needs certain capabilities, it must invoke the computational context of those capabilities (which is usually a monad in Haskell). If it doesn't, then the context can (and should) be omitted. It's worth noting that monad transformers are themselves monads. So drawing a distinction between "monads" and "monad transformers" to say that one is good and one bad is not very meaningful. The composability of monads, as exemplified in MTL, Transformers and similar, is a positive sign of their power and not a red flag. If your code ends up with an "unwieldy tower" of transformers then that's a strong indication that the principle of separation of concerns has not been adequately followed. The fact that Haskell makes that evident seems to me like a benefit.
- deleted 6y ago[deleted]
- misja111 6y agoEven if you have separated concerns by having method 1 return Monad transformer A and method 2 return transformer B, you will still have to combine the results of both your methods at some point.
- lmm 6y agoSure. But if you're doing it right, that combination happens in a place whose sole responsibility is doing that combination. The only case where you have to carefully interleave A and B is if your business logic really does involve carefully interleaving two disparate effects, and in that case the complexity is genuinely there in the domain and it's good to surface it explicitly (or else you're modelling it wrong).
- mcguire 6y agoTrying to explain monads is misguided. The formal definitions do not help practical understanding (at least without a strong background in category theory, maybe?). It's better to simply use them until you get the idea: the IO monad is the same sequencing operation as in any traditional programming language, state monads are slightly weaker versions of the same, maybe and error monads are short-circuiting evaluation, and list is much less weird once you get that (and see the "Turn your failures into a list of successes" paper), and by that time you're probably deep into monad transformer territory. Once you get comfortable with those, you're good to go. Unless the Haskellers have created a bunch of new nifty abstractions to learn.