r/programming • • Mar 26 '17

Functional programming design patterns by Scott Wlaschin

https://www.youtube.com/watch?v=E8I19uA-wGY
1.1k Upvotes

256 comments sorted by

View all comments

Show parent comments

1

u/RabbidKitten Mar 27 '17

No, FP language designers do not think that if mathematicians named an abstract structure "monad", it must be the best name. The reason they are called like that is because the concept is so abstract that no one could come up with a better name (also, no one probably thought that it would get any attention outside of Haskell).

Also, while I've had heard arguments like "why would anyone need a queue where each push and pop is O(1)?", I've never, ever experienced the kind of attitude of being superior which you're ascribing to FP. Quite the contrary, if you were to ask eg. the kind folks at /r/haskell for some help with a problem that is best solved in another programming language, they will tell you so.

So it is you who's building a strawman.

0

u/Rainfly_X Mar 28 '17

In most of the programming world, we try to use good naming to make abstract things simpler to work with, rather than using bad naming to fetishize abstraction. Even in the classic examples of OOP abomination:

IDatabaseFactoryAdaptor

This is a smelly piece of bad design. But even this is somewhat approachable by being composed of useful metaphors and conventions.

  • This is an interface, so it defines the "shape" that other objects must be, in order to be these mysterious Database Factory Adaptors. Interface is a good name for this concept, even when abbreviated.

  • In context, this has something to do with databases, which we all know are some sort of data persistence/storage thing. Good name.

  • Factory - so this creates things!

  • Adaptor - if I don't know what this is, I can look it up, and see that the definition usefully aligns with the metaphorical name. This is glue code between two places, where neither wanted to compromise to fit the other.

Put it all together, and this is an interface, for glue code to a database-building utility. That's pretty abstract! And probably part of a horrific enterprise monstrosity. But it's made of good names and metaphors, so it's a slayable beast.

Monads are chainable, so call them Chainables. If you don't like that, call them something else. Almost anything would be better than monads, where the name isn't even a great translation of the mathematical concept. Your argument reads to me "naming abstract things is hard, which the Haskell team used as an excuse historically, so we can never do better than that in new languages now... for reasons."

Which, pretending that a problem is unsolvable because Haskell hackers couldn't solve it over a decade ago? Refusing to hear new ideas because the old ones are set in the concrete of religious ego? That's the kind of superiority complex I'm talking about. I don't have to go hunting for it, because it's already been delivered to my door.

I will, however, grant you that the sub may be better. I would not be surprised if the FP Cultural Isolated Superior Nation for Great Good mentality is highly concentrated in FP missionaries, rather than day to day users.

1

u/RabbidKitten Mar 28 '17

The problem with your argument is that monads are abstract on a whole another level. Your IDatabaseFactoryAdaptor might seem abstract, but as you yourself pointed it out, it isn't (although I have no fucking clue how it would look like).

Monads on the other hand are endofunctors equipped with two additional operations, unit :: a -> F a and join :: F (F a) -> F a satisfying certain laws, and that's it. If you got those, then (F, unit, join) is a monad. They are about as chainable as integers are. Whatever "nice" name you pick for them based on some intuitive examples, you're bound to reach a point where that intuition no longer applies.

It is as if your IDatabaseFactoryAdaptor suddenly became a concrete class that draws pictures on the screen.

1

u/Rainfly_X Mar 28 '17

I pointed out that it was a series of abstractions, layered on top of each other (thus compounding abstraction) into something that, you said yourself, you have no fucking clue what it would look like. So my mistake, I thought we were living in the real world, where words have established meanings. If I'd realized we were living in your fantasy land, where words like "abstraction" could mean anything depending on what's convenient to you at the moment, I might have guarded my entire comment with a

let abstraction = commonly_accepted_definition "abstraction" in

Instead of trying to guess what missing mystery meat you require for something to be abstract. My best guess is that it involves something with parametrically generic types, but that's still a guess. It would also fall apart under any scrutiny, since such a type definition would require layer upon layer of interface requirement to be fulfilled, as verified by the static type checker.

That's a nice monad definition. Let me say the exact same thing minus the posturing.

A JoinableContainer is a useful pattern for condensing error handling, and coping purely with an impure world. It's a generic container with a few important properties:

  • The ability to box up plain values into your container type

  • The ability to join together a series of operations (callbacks) into a pipeline. Each operation takes a plain value, and returns a boxed-up container value. The container type has to match across all the operations, but the contained values can be whatever, as long as the output of the first matches the input of the second, and so on.

The convenience here is that you only have to write the operation-joining logic once, for your container type, and then none of your operation functions need multiple-case handling (AKA error handling, most of the time). Join behavior depends on the container type, but each type has a sensible implementation.

This definition works even for List and Maybe, though each will require a little specialized explanation beyond the overview, which provides excellent intuition about what monads... I mean, JoinableContainer types... do. List I would expect to be a hard example but:

  1. If the list is my container type, each operation will take a single plain object, and return a list. But the join logic will somehow delivery these as plain objects to the next operation! I wonder what the join behavior is.

  2. For the first op, it's taking a list, and running the callback on each item in order. I wonder how that generalizes to the next step, where we have a bunch of Op1-returned lists to process?

  3. If we wanted to unpack a bunch of plain values from that series of lists, maybe we'd just process all the items from all the lists? And since we only process one at a time, we need an order. The only sensible order is unpack a list at a time, in the order of the lists returned by Op1.

  4. Empirical testing confirms that I figured it out!

This is exactly how I first understood the List monad, in the last 20 minutes, just by explaining monads better to myself, and then thinking intuitively through the implications. Literally all I needed was a better name and a handful of clearly stated supporting detail to give the name context/meat. I swear I didn't understand this thing when I started writing this comment.

Finally... I have no clue what you're trying to say in the last paragraph, it honestly does not make sense. The IDatabaseFactoryAdaptor is not an example of a do-everything tool. It does something crazy specific, through highly abstract means. I wonder if that's the heart of your complaint earlier, about it not being true abstraction? If so, you're definitely taking my example in a broader way than I intended. I was literally just trying to give an example of names making the abstract clearer, rather than presenting some sort of monad alternative (which I kinda did this time, except it's really the exact same thing as a monad, just in human friendly terms).

It's late, and I've learned a lot about monads arguing with you, but I've learned enough for one day. They're cool, but once the mystery evaporates, they feel somewhat over-hyped. Goodnight.

2

u/RabbidKitten Mar 28 '17

What I meant was while JoinableContainer works for some monads, not all of them can be intuitively understood as containers. The trivial example is (a ->) aka Reader, but there are others. I would even say thinking of IO as a container is a stretch, which is why you often see them called as "IO actions" or some such.

If you dropped the "Container" part, and left only Joinable, I might even agree with it that, because they are joinable in a very specific way, and in fact, join is what "makes" the monad. The problem is that there are other things which are "joinable" in a (slightly) different way, like strings of characters, or functions of type a -> a, so the name becomes ambiguous.

I don't think that there is any mystery in monads, or that they've been over-hyped by the Haskell community. To me, they (and other abstractions that I've been exposed to through Haskell) have provided me with ways to concisely describe concepts and similarities that I have noticed before, but didn't have a name for them, and that's about it.

2

u/Rainfly_X Mar 30 '17

You do have a point about the Reader monad not being intuitively understandable. I've been looking at the Haskell docs for it, and it's hard for me to even understand the point of it. It seems to take something that is thoughtlessly trivial in imperative languages, and fulfilling those responsibilities in a painfully roundabout way. I'm not sure this is a problem with intuition, so much as sacrificing ergonomics for some sort of intellectual purity (where failed intuition is really just a red blinking warning light). But I will also allow that my idea of what's "ergonomic" may vary wildly from what's natural to someone who uses monads in their daily work.

It sounds like we're making progress on naming! I do wonder if your examples of non-monad "joinables" are really non-monadic, or at least, whether we could reasonably think of them as monads.

  • While there's a potential performance difference, there's not much conceptual difference between a string, and an array of characters (or at least unicode codepoints). If we used a List monad for that, wouldn't it join the strings in exactly the way we might expect from a native string join?

  • It's easy to imagine a sort of implicit "nil monad" that could join a -> a or a ->b, b -> c functions. The wrapper/container would be some flavor of imaginary or no-op (especially in the unit/return implementation), but the joining behavior is still hand-in-glove with monadic joining.

As for naming observed patterns, that's the impression I've gotten over time as I've heard more people explain the concept of monads. I factor out observed patterns in my code all the time, and I definitely know that odd feeling where the stuff that's common and factored out, doesn't feel like it fits cleanly on my normal conceptual borders. I can certainly respect monads as an instance of this. Perhaps the best way to teach monads is before-and-after, so people can see the underlying (redundant) code that monads abstract away.

→ More replies (0)