[HN Gopher] Functors and Monads for People Who Have Read Too Man...
___________________________________________________________________
Functors and Monads for People Who Have Read Too Many "Tutorials"
Author : zdw
Score : 330 points
Date : 2021-06-26 00:09 UTC (22 hours ago)
(HTM) web link (www.jerf.org)
(TXT) w3m dump (www.jerf.org)
| baryphonic wrote:
| The functor description is quite good.
|
| If the author is reading this, I might make a suggestion: connect
| the hash map example to the function example.
|
| Mathematically, a function is typically defined as literally a
| (potentially infinitely-long) map of unique keys (domain) to
| values (codomain). In programming, the two are not identical,
| especially in mutable languages, but the hash map example would
| be like memoizing function composition for a set of inputs
| specified as keys in the hash map.
| juliangamble wrote:
| This post brings me back to this idea for industrial programmers:
| https://www.simplehaskell.org/
| Arnavion wrote:
| >If They're So Wonderful Why Aren't They In My Favorite Language?
|
| While monads as a general interface might not be in your favorite
| language, individual implementations of monads might very well
| be. JS Promise, C# Task and Rust Future are all asynchrony monads
| - their bind is .then / .ContinueWith / .and_then respectively
| (with some caveats; the first two overload their bind to also
| work as fmap).
| Kwantuum wrote:
| It's not just "some" caveats: then auto-unwrapping means that
| you cannot use then as fmap reliably. In JS there is no such
| thing as Promise<Promise<Number>>. You cannot await/then the
| outer promise so that you can do something with the inner
| promise.
|
| In practice it's not too much of a problem, but it does mean
| that if you use some FP library, you cannot trivially wrap a
| promise to make it a monadic type that will work with the
| library's monad utilities.
| dasyatidprime wrote:
| So, just out of curiosity: I know in ECMAScript this is
| probably _mainly_ for the purpose of giving you a convenient
| way to lift _either_ (a - b) or (a - Promise b) into (Promise
| a - Promise b) without static typing or having two "then"
| functions, by forcing b and Promise b to be disjoint. It also
| allows looser composition underneath, since you can choose
| which kind of return to provide at runtime without having to
| re-wrap the bare case manually. But are there any _other_
| reasons for Promise of Promise to be squashed away like that?
| Arnavion wrote:
| Apart from simplicity, it was designed to be used with
| "thenables". If the value that a Promise resolved with had
| a `then()` member, the value would be treated like a first-
| party Promise. So even if there was a separate fmap-then
| and bind-then, the fmap-then would have to deal with
| thenables and act like bind-then anyway.
|
| There's a bunch of history in [1], particularly [2] and
| [3], if you care to read them.
|
| [1]: https://github.com/promises-aplus/promises-spec/issues
|
| [2]: https://github.com/promises-aplus/promises-
| spec/issues/75
|
| [3]: https://github.com/promises-aplus/promises-
| spec/issues/94
| [deleted]
| skybrian wrote:
| This part could be more simply explained:
|
| > The problem is that a "do" block in the list monad chains
| together several of these flatMap calls. It is very easy for the
| transformation function to result in the computation growing
| exponentially as each one expands one item from before into one
| or more items. You have to have something else in the problem
| that prevents that from happening. You may see a lot of cute
| little algorithms like solving the n-queens problem, but in
| practice, this is not something you see used a lot in Haskell.
| The mathematical transform is cute, but hard to control because
| it's a bit too simple. In particular, this has no ability to
| "notice" that it is revisiting possibilities it visited before.
| That would take more machinery.
|
| I think he's saying that it's doing a depth-first search without
| any caching. I'm wondering if Haskell has some other nice way of
| specifying depth-first searches that performs better?
| themk wrote:
| Quite often what you want is LogicT which gives you more
| control and can significantly improve performance over List.
| baby wrote:
| What I find annoying in OCaml and functors is that it makes code
| really hard to follow. You're wondering how that function is
| defined or what exactly is that type? Now you have to try to jump
| through many functors, their implementation and their
| instantiations. If IDEs could facilitate that process it'd be
| fine, but they can't atm.
| rocqua wrote:
| I loved this. It helped me get monad better, and improved my
| understanding of functor.
|
| What I am still unclear on is the difference between applicative
| and monad. Both, to my mind, have this concept of combining
| "wrapped" values. Currently my intuition for the two is exactly
| the same. So my intuition is probably wrong.
|
| I would love a similar explanation to this post on applicative vs
| monad.
| AnimalMuppet wrote:
| Thanks, jerf! This is _really_ good stuff.
|
| One question on functor: Does the thing returned have to be "the
| same shape" as the original? That is, if I have a list, and I
| fmap on it, do I always get a list back? Or am I only guaranteed
| to get some kind of functor back? If I am guaranteed to get a
| list back, am I guaranteed that it has the same number of
| elements?
|
| To use your initial language, is it fair to say that a functor
| takes a source of a, and a function that converts a to b, and
| returns a source of b _of the same "shape" as the original source
| of a_?
|
| If it's true, that makes the function example clearer to me. Of
| course it _has_ to return a function, because the shape of the
| input was "function".
|
| (And, did the article answer this and I just missed it?)
| yakshaving_jgt wrote:
| The answer to your first question is yes. You can see that in
| the types. fmap :: (a -> b) -> f a -> f b
|
| The `a` becomes a `b` (which could be a different type but not
| necessarily), but the `f` is the same `f`.
| mLuby wrote:
| > For their own good reasons, mathematicians use "functor" as a
| concrete noun. Haskell copied this, hence how often you hear
| about "a functor".
|
| > However, interfaces are adjectives that describe a data
| structure, which is why in many languages they are often named
| with -able suffixes (Iterable, Serializable, etc.) Programmers
| are better off thinking of it as Functable or Fmapable or
| something like that.
|
| Fmapable makes _much_ more sense to me. Naming is hard.
| revskill wrote:
| Actually, just learn the construct `Promise.then().catch()` and u
| already know how to use Monad in your code. The `then` is the
| `bind` of Monad. What's complicated here ? Want `do` syntax ?
|
| a <- method b
|
| c <- method a
|
| is equivalent to
|
| a = await method(b)
|
| c = await method(a)
|
| Again. Use javascript to learn Monad in real world is a better
| way to learn.
| Dylan16807 wrote:
| That doesn't tell me a single thing about what 'monad' means.
|
| It's like having someone run hello world in java and declaring
| they know what 'static' means.
| wyager wrote:
| That's an example of a specific monad, not the definition of
| the general concept of a monad.
|
| Also, JS promises are not really monads. They lack denotational
| equality, so they can't be said to obey the monad laws.
| valenterry wrote:
| The article doesn't mention lawfulness until the very end. But
| lawfulness is a crucial part of the definition and usage.
|
| Because, a lot of the convenience and ergonomics that we can
| derive using these abstractions comes from being able to rely on
| the laws. Without them, they are just not really useful.
|
| And when looking at the examples, I can totally see that someone
| reads
|
| > That's all Monad is. It is an implementation of some "method"
| that conforms to that interface specification.
|
| and then makes "Set" or "HashMap" implement the interface
| unlawful and bad things happen when they are least expected.
| Osiris wrote:
| What do you mean by lawful? This is the first time I've ever
| heard someone use that term in this context.
| AnimalMuppet wrote:
| Monads should satisfy the "monad laws": left identity, right
| identity, and associativity.
|
| IIRC, Haskell relies on two out of the three, but I don't
| remember which two. But if you write a "monad" that doesn't
| satisfy them (even though it has the right function
| signatures), then you're going to get bogus results.
| loopz wrote:
| Time for an analogy and why math is supreme:
|
| x+y+z is associative.
|
| x-y-z is non-associative.
|
| Functor, monoid and monad laws allow for undefined
| evaluation order, lazy evaluation, parallell execution and
| same results for same parameters. But only if the laws
| holds can such be guaranteed when using certain
| abstractions. Associativity being one obvious caveat that
| might break abstraction over collections, while using
| divide & conquer mechanics such as function currying.
| valenterry wrote:
| In addition to what has being said, if you are a Java
| developer, then this article will be helpful for the
| understanding: https://www.sitepoint.com/how-optional-breaks-
| the-monad-laws...
|
| It gives a lot of theoretical background, but if you read the
| article here and already know the Optional-type and then you
| can skip it and go directly to "A Real World Example".
| dllthomas wrote:
| Monad and Functor make some additional promises, beyond what
| can be expressed in the types, that some users rely on.
|
| In the same way that we expect addition of numbers to obey
| the commutative law and associative law. (Except, of course,
| that floats don't...)
| valenterry wrote:
| Floats are a great example actually.
|
| Implementing "unlawful monads" would be equal to giving
| someone a float and telling them "you can use this like a
| rational number from math". And in fact it might work for
| the longest time - until it doesn't (think rounding-errors
| and similar) and then the confusion will be big.
|
| With monads it's the same, but potentially even worse,
| because it's rather easy to test float addition behavior,
| but for certain monads that can be much harder.
| marcosdumay wrote:
| Haskell has a library called monad-validate that I think
| is a great example for both what the monad laws are
| important for, and how it's important that one understand
| their reasons, instead of just following advice literally
| on every possible situation.
|
| Just as yo want to break addition commutativity so you
| can approximate real numbers, there are times when you
| want something to behave like a monad, but not follow its
| laws.
| BoiledCabbage wrote:
| Pre-conditions and post-conditions of API calls would be a
| rough analogy - although fairly inaccurate.
|
| Ie it's not just the interface that matters, but rules about
| the implementation.
| jerf wrote:
| In my opinion, in a programming context, that's Haskell
| specific. Most programmers in other languages won't worry about
| the laws. The laws are less useful and less relevant in strict
| languages, as is the case with many laws, which is why Haskell
| programmers are just about the only ones discussing them.
|
| I label this my opinion quite on purpose, because I see the
| alternative viewpoint. I think you'll be more successful taking
| about lawfulness once someone gets the mechanics, given how
| much that has been a stopper over the years!
| drdeca wrote:
| I don't know why just saying the following block of text
| combined with walking through a bunch of examples and how
| each of the examples fulfills those properties, wouldn't be
| sufficient : ' If F is a functor, then for any A,B,C,f,g,
| such that f : A -> B , and g : B -> C, (where we write
| composition with a semicolon so that the composition of f and
| g is written f;g : A -> C), F(f) : F(A) -> F(B) and F(g) :
| F(B) -> F(C) and F(f;g) : F(A) -> F(C) and F(f;g) = F(f) ;
| F(g) (also, if id_A : A -> A is the identity on A, the
| F(id_A) : F(A) -> F(A) is the identity on F(A) ) '
|
| (in case they are unfamiliar with the notation f : A -> B ,
| then this can be explained in each of the first few
| examples.)
|
| I guess it might also be good to give some counterexamples,
| and point out how the counterexamples fail to be functors,
| and why they are also not nice.
| valenterry wrote:
| Don't get me wrong - I'm not saying the article is bad or not
| useful.
|
| However, Monad and Functor are well defined terms. If you
| write an article where you complain about confusing
| explanations and try to do better, then you _must_ start with
| a disclaimer: "I'm not explaining Monads as they are defined
| in math. I'm explaining something that is quite similar to it
| to make it more practical and later explain the actual term"
| or so.
|
| Otherwise you are just adding to the confusion that people
| experience.
|
| > The laws are less useful and less relevant in strict
| languages, as is the case with many laws, which is why
| Haskell programmers are just about the only ones discussing
| them.
|
| I'm not sure if I would agree with that. But even if I would,
| the laws are still really important, even in strict
| languages. I've implemented quite a few monad instances in
| Scala (which is a strict language) and I'm speaking from
| experience.
| TheFreim wrote:
| > Title is literally true. This may not be the best place to
| learn about these concepts for the first time, because I'm going
| to focus on knocking down the misconceptions about them.
|
| I've recently been interested in learning more math to apply to
| programming. Does anyone here on HN know where I can find quality
| education online (free) I can use through the rest of the summer?
| I only got to geometry as my highest maths in high school.
| malwarebytess wrote:
| This is precisely how my Programming Languages CS Professor
| explained it. Probably contributed to Haskell being my Go To
| language.
|
| This dialectic style adopted by the article for explanation is
| effective in exploring confused understandings. (Bit socratic)
| Not all writing needs to be technical writing like in a manual --
| narrative explanations can be useful too.
| chobytes wrote:
| To be honest I feel like the functor concept is too abstract and
| subtle to the point where it's probably not very useful in the
| context of programming.
|
| Worse... their usage in programming languages is very unlike how
| homomorphisms are typically used in their usual context, so
| appealing to the math seems like its just confusing learners
| without payoff.
|
| To give an analogy... it feels like trying to explain the chain
| rule by explaining pullbacks between tangent spaces. Not
| technically wrong, but now they're confused and asking questions
| whose answers wont help them.
| siraben wrote:
| Of course, the chain rule explained via differential topology
| is simply functoriality!
|
| IME, category theory was much more valuable in differential
| topology in helping solidify why the structures and their
| compositional properties make sense. You get to work with
| several, honest-to-god categories and functors between them,
| especially when tangent bundles come into the mix.
|
| Now go to programming, there's really only one category at
| play, and I'm not sure how worthwhile it is to apply a (more
| contrived) version of CT that only has endofunctors and being
| cartesian closed.
| chobytes wrote:
| Haha, I feel like thats how I started to get CT concepts too.
| Topology and geometry really feel like the "natural" context
| for those ideas.
|
| For programming... I feel like they're basically trying to do
| logic in a roundabout way. I _suspect_ that (finite) model
| theory might be more useful for such applications if they
| really want theory.
| the_af wrote:
| > _it 's probably not very useful in the context of
| programming_
|
| This puzzles me. It turns out it's _very_ useful in the context
| of programming _in practice_ (i.e. Haskell programmers use it
| and find it useful), so what do you mean? I don 't know CT so I
| wouldn't know how well it maps (pun intended) with the Math
| concept, but regardless, whatever abstraction is there in
| Haskell that programmers call "functors" is tremendously
| useful.
| creata wrote:
| `Functor` instances _need_ to exist: they 're obviously
| useful. You'll see a million `map` methods in every
| programming language.[0] What you _won 't_ see is a
| unification of them all under one interface. And the point,
| in my view, is that this unification doesn't really buy you
| much. I can't think of a single algorithm that starts with
| "let T be a mappable type".
|
| [0]: https://doc.rust-lang.org/std/?search=map
| loopz wrote:
| CT may be appropriate to further evolve Haskell as a
| language, but for programming implementations; mathematical
| categories aren't directly useful. There Haskell is more like
| any other programming language, just that it's FP.
| the_af wrote:
| I'm not debating that; instead, I'm arguing _functors_ are
| immediately useful in languages like Haskell. Don 't know
| enough about CT to debate that point.
| loopz wrote:
| They are. Abstractions like functors makes the language
| more similar to other structured languages. Otherwise,
| the same would've needed explicit recursion all over the
| place, which is more error-prone.
| chobytes wrote:
| The concrete instances are demonstrably useful, since
| theyre... actually used! However in Haskell theyre basically
| one kind of very concrete object. Invoking the abstract
| machinery of CT to explain list concatenation just has the
| hierarchy backward. Its like telling someone that inner-
| product induced norms on hilbert spaces are extremely useful
| when youre talking about taking the absolute value.
| creata wrote:
| > To be honest I feel like the functor concept is too abstract
| and subtle to the point where it's probably not very useful in
| the context of programming.
|
| I think it's a small convenience to have the programming
| language automatically derive `map` instances for you, but I
| think you're right for pretty much the same reason group
| theory's huge but monoid theory barely exists: the `Functor`
| typeclass is too simple to do anything interesting with it.
| WastingMyTime89 wrote:
| I think the reason so many people have trouble understanding
| functors and monads is because they are not complex constructions
| but look useless when you see them.
|
| There is nothing inherently interesting about functors and
| monads. Haskell, as a research project, discovered that you could
| isolate IO in a lazy language inside a type using a monadic
| construction to chain them which allows you to track side
| effects. That was interesting, the rest less so.
|
| Now the Haskell community somewhat fetishized these abstractions
| for reasons which are their own. They must be apparent to the
| community at large. They are not to me.
| hu3 wrote:
| > Haskell, as a research project, discovered that you could
| isolate IO in a lazy language inside a type using a monadic
| construction to chain them which allows you to track side
| effects.
|
| Sounds like LINQ in C#. I wonder if there's any correlation.
| loopz wrote:
| LINQ is being worked on by the same people
| developing/designing Haskell. These methods are slowly being
| injected. LINQ is monadic and has a bind function that
| generalize beyond IEnumerable.
| zem wrote:
| the reason for "fetishizing abstractions" is that you can write
| libraries that work with the generic structure of those
| abstractions, and are therefore automatically useful for all
| the specific cases. the design of ruby's "Enumerable" library
| in the standard lib is a good example, it's a huge collection
| of methods that works for anything that supplies an 'each'
| iterator.
| void_mint wrote:
| I have enjoyed Jerf's content for years. I really enjoy this
| content as well. Jerf if you read this, excellent stuff, thank
| you.
| DanAtC wrote:
| Articles like this and the subsequent comments are just a long-
| running, inside joke of who can out-pretentious one another,
| right?
| loopz wrote:
| Wether you know functor laws, or you misapply functors, you
| have to talk about it to be properly applicative ;)
|
| The community is very much focused on learning, by teaching the
| little one knows.
| bane wrote:
| Haha, I read this originally as "Functors are Monads from the
| class of People who Have Read Too Many 'Tutorials'"
| cole-k wrote:
| I agree wholeheartedly that Functors and Monads should be thought
| of as interfaces. And that much of what complicates them is
| separating the implementation details from the interface details.
|
| However, I can't help but feel that the author and I have some
| common flaws.
|
| The first being verbosity. I often notice the same thing in my
| own writing: I will see a well-written paragraph and think "this
| is good stuff" and then wonder "but what purpose does it serve
| here?" Stuff like dedicating 5 paragraphs to discussing whether
| Functor should be a noun (I get the point, but that's a lot of
| words to make it), or a paragraph with caveats about how Haskell
| does name resolution.
|
| The second being that we both seem to have fallen victim to the
| monad tutorial fallacy
| (https://byorgey.wordpress.com/2009/01/12/abstraction-
| intuiti...). But I'm not 100% sure about this one. I don't think
| it's wrong to call monads interfaces in the same way that it's
| wrong to call them burritos. However, our framework of thinking
| does seem to align with what that author refers to as fallacious.
|
| Still was a good enough read that I felt the need to comment
| partway through :)
| Dylan16807 wrote:
| > the monad tutorial fallacy
|
| The paragraph starting with "I am aware of the notorious
| effect" directly addresses this, do you disagree with it?
|
| Also calling it an interface _isn 't an analogy_.
| cole-k wrote:
| > The paragraph starting with "I am aware of the notorious
| effect" directly addresses this, do you disagree with it?
|
| I suppose my answer should be "yes," but let me try to
| explain my convoluted reasoning:
|
| Understanding monads simply in terms of interfaces is what I
| think is the silver bullet to comprehending them. It's what
| helped me "get" them. So they are my "burrito."
|
| > Also calling it an interface isn't an analogy.
|
| I agree, I apologize if I came across this way.
| jerf wrote:
| The verbosity is a direct effect of the several discussions
| I've had over the years on this topic. It has actually had a
| number of words removal and paragraph revival passes done on
| it.
| cole-k wrote:
| That's fair, and I hope that I didn't come across too harsh
| in my comment. I elided a paragraph of praise I thought was
| redundant to my first few sentences.
| ectopod wrote:
| I found that section really helpful. I've been writing
| Haskell (on and off) for years but I hadn't realised that the
| noun thing had created a sort of mental speedbump, which is
| now gone.
| xupybd wrote:
| It helped me to understand what is going on. I think I've
| been hung up on functor sounding like a noun for a long time.
| I thought there was some extra magic I wasn't getting that
| made it into a noun. The verbosity helped me a lot.
| thewakalix wrote:
| I think it is at least _somewhat_ wrong to say that monads are
| just an interface. For instance, when the author says
| (paraphrased) "you could remove every trace of monads from IO
| and it would still be usable, albeit clunky", they're actually
| talking about removing the monad _interface_ , but not the
| monad _structure_ -- it would still rely on a function of type
| (IO a - > (a -> IO b) -> IO b) that's expected to follow
| certain rules. In my book, that _is_ a monad -- whether or not
| it's called "monad", and whether or not it's formalized as
| such.
|
| (This isn't an entirely new kind of concept in programming,
| either; see "patterns".)
| ledauphin wrote:
| i am interpreting this as "monads are a structural type, not
| a nominal type".
|
| which to me is an interesting debate given Haskell's emphasis
| on strong interfaces strictly enforced.
|
| We seem to be converging on a world where we agree that
| declaring and enforcing types is very important, yet also
| that it's the structure of the type, not its name, that is
| most critical. At some point, with sufficiently advanced type
| inference, do you end up in a world that looks like a lot
| like fully dynamic languages, where the actual declaration of
| the type is purely optional?
| zzo38computer wrote:
| I don't really like the description of the IO type so well.
| Rather, I can say that the type "IO x" is the type of I/O
| operations that produce a result of type x. The functor/monad
| functions that can be used include:
|
| - "return x" produces a I/O operation that does nothing and
| produces the result x.
|
| - "x <$> y" or "fmap x y" produces a I/O operation that does the
| same thing as y, but the result value is the function x applied
| to the result of y.
|
| - "x >>= y" produce a I/O operation that when performed, performs
| x, and then passes the result to the function y, which returns
| the I/O operation to be performed next.
|
| - "join x" takes the I/O operation x which has the result which
| is itself a I/O operation; the new one first does x and then the
| I/O operation which is the result of x.
|
| A I/O operation might have different results when performed
| multiple times, even if it is the same I/O operation.
|
| Also, I can say that generators in JavaScript seem to form two
| different monads actually (as far as I can tell), as described by
| the following table: yield x yield*x
| return x return yield*x
|
| Also, functors and monads are more than the interface, but also
| the mathematical properties they have (such as "fmap id = id"
| etc).
| lambdasquirrel wrote:
| After years of seeing people try to understand abstractions like
| a functor from the definition first, I've started to think it's
| the wrong way to do it.
|
| This post helps on some of these points, like explaining that
| e.g. functors are not always containers.
|
| I'd love to see someone try taking it further: just hashing out
| lots and lots of _substantially_ different examples, showing how
| they are similar to each other, and progressing into higher level
| transformations, before finally showing the definition of a
| functor, and how it maps to each of those cases. Forget monads.
| Can we just start with functors and see how far we get with that?
| nerdponx wrote:
| > I'd love to see someone try taking it further: just hashing
| out lots and lots of substantially different examples, showing
| how they are similar to each other, and progressing into higher
| level transformations, before finally showing the definition of
| a functor, and how it maps to each of those cases. Forget
| monads. Can we just start with functors and see how far we get
| with that?
|
| I think this is how a lot of people learn math and programming
| anyway. Build theory out of intuition.
| EForEndeavour wrote:
| Sadly, in my experience, math is often taught theory first,
| followed by almost trivial or an insufficient number of
| examples, with more helpful examples left as exercises for
| the reader with no solution provided.
|
| Post-secondary math classes were actually amazing to
| cultivate the habit of persisting through long periods of
| feeling like a complete idiot. But I have to wonder what the
| same content would have been like had it been example-driven.
|
| It reminds me of learning a language through exposure to
| endless examples and internalizing rules without being
| explicitly taught them, versus deliberately memorizing the
| grammar rules of a foreign language and then applying the
| rules to specific examples.
| rightbyte wrote:
| I feel you ... going from high school to university math
| was a shock for me. Almost all proof and no handcraft.
| Going to lectures was a waste of time since I had no chance
| of understanding. I studied mechanical engineering, not a
| math program!
|
| How I actually learned the math was by examples and easy
| exercises, not understanding proofs.
| cartoonworld wrote:
| >Sadly, in my experience, math is often taught theory
| first, followed by almost trivial or an insufficient number
| of examples, with more helpful examples left as exercises
| for the reader with no solution provided.
|
| Ugh. So much this.
|
| I've had so much difficulty grokking mathematical concepts
| and procedures in my life. In high school, I fell behind so
| hard.
|
| Algebra I was doable, and while learning Geometry I felt
| like I already knew everything about the subject. I never
| studied and the admins concluded that I must be cheating,
| given my performance in other courses.
|
| I concluded that in order to learn these things, I had to
| have "something to do with it". So to speak, some relatable
| examples to which I could apply the concepts in order to
| internalize the rules. This strikes me as the source of the
| never-ending choruses that are echoing still through so
| many study halls. ringing out, "WHEN ARE WE GOING TO NEED
| THIS???"
|
| If feels so good when the concepts click, it's like
| learning you had a superpower. Suddenly, the baroque,
| mysterious walls of lines instantly resolve into such crisp
| focus, it truly is an amazing epiphany, Ah-Ha!
|
| I feel like I could have really enjoyed the discipline, but
| I never achieved this feat in my educational career. I had
| problems that dramatically redirected the course of my
| life. This is probably why computers appealed to me so
| much, because of the direct cause-and-effect nature of
| interfacing with them. The Hacker Manifesto comes to mind,
| but I digress.
|
| I have Narcolepsy Type II and probably ADHD (as you may
| infer from my comment history) and let me tell you...
| dietr1ch wrote:
| I really like when they present examples and
| counterexamples to help you realize the subtleties of the
| definition.
|
| Often when presented with a definition it's easy to get
| fixed with examples that are too specific and then to
| conflate the definition with something a bit stronger.
| ducaale wrote:
| I have enjoyed reading https://dev.to/choc13/series/12008 which
| uses real world examples to explain Monads, Applicative,
| Lenses, etc.
| ggm wrote:
| Thanks. Well written. Readable.
|
| This sentence from it sums up many of my conversations about FP
|
| _It probably also doesn 't help that we have a function
| literally called >>=, which is hard to pronounce. It's pronounced
| "bind", which is only slightly more helpful in that that still
| fails to evoke any useful imagery_
| jtwaleson wrote:
| I strongly dislike Haskell's focus on theory. Just show me
| examples on how to get things done and then the concepts will
| become clear. The Haskell and mathematics communities too often
| act like ivory towers that humans have to become enlightened to
| understand, and that simply is not how my brain is wired. I'm not
| sure of there have been any studies on the usability score of
| languages, but I think that Haskell would come out very, very
| poorly. There's a reason why python is so popular.
| baby wrote:
| I've been learning OCaml recently and have been really
| frustrated with the same issues. I've been writing this page
| for people like me who want to learn through examples:
| https://o1-labs.github.io/ocamlbyexample/ (still a work in
| progress).
| aszen wrote:
| If you want to just get things done, and then learn the
| concepts of fp, I always suggest using elm. In elm I had been
| using andThen functions without even knowing they were related
| to monads.
| sfvisser wrote:
| Python is probably easier to learn for most beginners, but that
| doesn't mean it's easier to write programs in. Context matters
| when talking about usability.
|
| I've used functors and monads to solve tons of issues and have
| literally no clue about the theory behind them.
| jtwaleson wrote:
| Agreed. Haskell solves real problems and it's quite a good
| language once you learn it, it's the cultural focus on theory
| rather than humans that I don't like.
| mchaver wrote:
| > it's the cultural focus on theory rather than humans that
| I don't like.
|
| That's completely fine. Haskell is not a single community.
| There are two big camps that often interact with each
| other, the academic and the industrial. Keep in mind the
| origin of Haskell is academic and it's original purpose is
| to test and implement ideas from Programming Language
| Theory. That is still there and will continue. GHC
| optimizes for letting people experiment with language
| extensions. Industrial interest didn't start to grow until
| somewhere around 2008-2012. It is a small community and
| will likely remain so.
|
| I am a big Haskell user myself. There are many theoretical
| things I don't understand or need to touch, but I
| appreciate their contributions to the language and the
| community. I do agree that Haskell has lots of room to
| improve on tutorials for non-academics. Maybe I will get
| inspired to write somethings.
|
| It's completely fine if you don't want to use it. However,
| you may find Haskell users' enthusiasm for the language
| insufferable, hehe. I do hope anyone who chooses to
| interact with the Haskell community finds us welcoming.
| mmis1000 wrote:
| But the tutorials simply ignores the real world use case.
| Lots of concepts are actually useful for solve problems
| (even in other language).
|
| You need to give the reader some real world example. A
| concept dangles in brain without connects to other things
| won't live too long. You brain is likely to `optimize it
| out` because it is unused.
| lallysingh wrote:
| I thought "Real World Haskell"did just that very well.
| pyrale wrote:
| There is no real focus on theory in the language itself, but a
| significant part of the community, indeed, enjoys it.
|
| For balance, here's another common opinion about tutorials:
| http://dev.stephendiehl.com/hask/#eightfold-path-to-monad-sa...
|
| > [...]
|
| > Read the monad definitions.
|
| > Use monads in real code.
|
| > Don't write monad-analogy tutorials.
|
| In the end, the two opinions about learning monads coexist, and
| people have done fine following either way.
| choeger wrote:
| Usable for what, though? If the criterion is correctness, I'd
| bet good money that Haskell would beat python in any fair
| comparison. Just because there are languages that let you
| forget the complexities of your domain doesn't mean these
| complexities vanish when you use said languages.
| amelius wrote:
| Haskell folks are just regular IT folks who missed their
| calling by not becoming mathematicians.
| inimino wrote:
| If you have engaged with the Haskell community (I mean,
| actually talked to folks as you learn or use the language) and
| you came away with this impression, I would be very surprised.
| To learn Haskell, just do it, you don't need theory. For people
| who are interested in it, it's there.
| smitty1e wrote:
| What I haven't ever really seen is a good Haskell Cookbook.
| All the references seem to head for the ozone. Show me dumb
| little grounded snippets.
| pyrale wrote:
| https://typeclasses.com/phrasebook
| lallysingh wrote:
| http://book.realworldhaskell.org/ does this in a tutorial
| style.
| [deleted]
| vishnugupta wrote:
| For the context: Haskell was 4th or 5th language I learnt and
| the first pure functional one. My graduation thesis work was in
| Haskell and its backend/compiler so at one point I was
| living/breathing Haskell.
|
| > ...too often act like ivory towers...
|
| I 100% agree with this. The Haskell community as a whole and
| various tutorials etc., aren't optimised for a new comer.
|
| For instance, you don't see a "stand up a web app in Haskell in
| 10 minutes" tutorials. Historically it may have worked in
| Haskell's favour to slowly induct learners, but in this age
| where languages are fighting for mind space I don't agree
| that's a good approach anymore.
|
| Simon Peyton Jones (SPJ), an earliest and a famous champion of
| Haskell coined the phrase "avoid success at all cost" in a
| certain context which may have had some role in making Haskell
| seem Ivory Towerish.
|
| A language, like any living being, needs to adopt to survive
| and thrive. It's unfortunate to see Haskell remain a niche
| language even after close to 3 decades.
|
| > and that simply is not how my brain is wired.
|
| This, however, I disagree with. I mean in a general sense, not
| specific to how your brain is wired. It all depends on the
| first couple of language that one learns and how they are
| learnt and taught. 10-15 years ago people would find it hard to
| grok Python's functional concepts. But as it began to be taught
| as the first language in universities and those graduates join
| the working population you see how it's super natural for them
| to grok Python.
| jtwaleson wrote:
| Some context: I was taught Haskell, Prolog and Java at
| university (2006-2010). Coming from chaotic JavaScript and
| PHP scripting all were hard to grok, but I enjoyed them all.
| After a couple of years only Java stuck in my working memory.
| So: I could program in Haskell, I've probably used monads and
| functors fluently, but the concepts still don't click. I have
| the same problem with grammatic concepts like adjectives,
| adverbs, and the more complicated stuff. I obviously use them
| all the time, but I'm not fluent in the theory.
| willtim wrote:
| > the phrase "avoid success at all cost" in a certain context
| which may have had some role in making Haskell seem Ivory
| Towerish.
|
| This phrase is often misunderstood, it's "avoid, success at
| all costs", not "avoid success, at all costs". In other
| words, don't optimise for mass market adoption at the expense
| of everything else. Languages that have arguably done so,
| have ended up as extremely complex and ridden with corner
| cases.
| tux3 wrote:
| The phrase is often taken as having a single true meaning,
| but I think the sum of them is really what makes the quote
| so good.
|
| It's easy to follow a rule too far and end up way
| overcorrecting the original problem. What I like is that
| both meanings balance each other :)
|
| Some popular mass-market friendly deliverables don't cost
| very much at all, avoid them and you're definitely avoiding
| success at all costs, but with both meanings at once this
| time.
| wsc981 wrote:
| _> This, however, I disagree with. I mean in a general sense,
| not specific to how your brain is wired. It all depends on
| the first couple of language that one learns and how they are
| learnt and taught. 10-15 years ago people would find it hard
| to grok Python 's functional concepts. But as it began to be
| taught as the first language in universities and those
| graduates join the working population you see how it's super
| natural for them to grok Python._
|
| That must be the reason why Edsger W Dijksta once stated [0]:
|
| _> "It is practically impossible to teach good programming
| to students that have had a prior exposure to BASIC: as
| potential programmers they are mentally mutilated beyond hope
| of regeneration."_
|
| ... and ...
|
| _> "The use of COBOL cripples the mind; its teaching should,
| therefore, be regarded as a criminal offense."_
|
| ... and ..
|
| _> "APL is a mistake, carried through to perfection. It is
| the language of the future for the programming techniques of
| the past: it creates a new generation of coding bums."_
|
| I must be one of those mentally mutilated programmers with no
| hope of regeneration :)
|
| ---
|
| [0]:
| https://www.cs.scranton.edu/~mccloske/dijkstra_quotes.html
| KronisLV wrote:
| I think that some of the quotes are pretty toxic,
| regardless of who they are by. Programming languages are
| just tools and as such, they should be reasonably easy to
| use. Programming languages should serve people, not the
| other way around.
|
| Elitism and gatekeeping do precisely nothing to help people
| solve problems with code.
| dgb23 wrote:
| > Elitism and gatekeeping do precisely nothing to help
| people solve problems with code.
|
| I agree 99% here. The 1% is where you want to shock
| someone to break through. Not sure if that was Dijkstra's
| intention, but I assume it was. He was a teacher as well,
| writing books, giving lectures. I think he was genuinely
| concerned.
| HelloNurse wrote:
| > Elitism and gatekeeping do precisely nothing to help
| people solve problems with code.
|
| Unfortunately, elitism and gatekeeping "solve problems
| with code" indirectly, by keeping inadequate people away
| from developer roles where they would make problems worse
| with negative-value contributions. Software mistakes are
| common and expensive: there are very strong incentives to
| predict and prevent them by following processes and by
| putting someone competent in charge.
|
| It is of course possible for elitism and gatekeeping to
| select the wrong sort of people (for example, not
| recognizing terrible people outside a manager's area of
| expertise), but even intolerable toxic attitudes (e.g.
| hiring graduates from certain universities) can
| effectively keep away dangerous people.
| KronisLV wrote:
| I'm not entirely sure that i can agree with that
| statement in its entirety, at least in the context of
| this particular type of toxic gatekeeping.
|
| If there are people who are inadequate at writing code
| and architecting software, then they should:
| - not manage to pass their university classes and not get
| a degree or a programming qualification - not
| manage to pass their internship by generating some value
| and proving that they're capable of learning and self-
| improvement - not manage to get the necessary
| certificates for a particular technology, as a vague
| proof of basic competency - not manage to solve the
| take home tasks that they're given by a company that's
| about to hire them or pass technical interviews -
| not manage to pass onboarding for some months and
| therefore should be fired - not manage to deal with
| their duties as a developer and therefore should be fired
|
| Of course, depending on different cultures, these things
| could change (e.g. bootcamp instead of university
| education, personal projects instead of certificates),
| but none of those involve calling someone: "...mentally
| mutilated beyond hope of regeneration," just because they
| used BASIC, PHP, Python, or any other _easy_ language
| that let them solve easy problems without getting too
| deep into the internals of programming languages and how
| computers work.
|
| Furthermore, the difference is in attitude - if a person
| doesn't pass one of the above, they can probably just
| upskill themselves and spend more time refining their
| craft, reading books, working on projects etc., whereas
| dismissing them entirely is likely to demotivate them and
| so they'll never achieve anything. It might also be
| selecting for the wrong types of people - those who
| simply brush off criticism like that and don't care, as
| opposed to those who are more sensitive, something that
| should hardly matter in regards to developing code.
|
| There has to be a better and more constructive way of
| criticizing people and even dismissing them: for example,
| saying "Hey, your programming knowledge seems okay, but
| you should work on your system design skills. Try
| applying for a job in a year again," vs simply ghosting
| them.
|
| > Software mistakes are common and expensive: there are
| very strong incentives to predict and prevent them by
| following processes and by putting someone competent in
| charge.
|
| Lastly, this feels like the job of: - the
| compiler, for errors and warnings - the IDE and
| language server, for general suggestions - the
| linter, for code style rules - static analysis
| tools like SonarQube, for additional code checks -
| testing frameworks (including code coverage gates), for
| unit, integration and end-to-end tests - manual
| code reviewers, after everything above has been resolved
| by the code author - QA specialists, after all of
| the previous checks have been successfully passed
|
| As for having competent leaders, sernior developers,
| architects, security specialists etc., i wholly agree!
| That's not to say that the culture of software
| development needs to be a parody of anti-social
| behaviour.
| rualca wrote:
| > I think that some of the quotes are pretty toxic,
| regardless of who they are by.
|
| They are indeed, aren't they?
|
| I mean, they straight up sound like bullying those who
| happen to not think alike or share the same opinion.
|
| Those who are in the right are able to form rational
| arguments, raise concerns about specific problems, and
| offer solutions. But no, not in this case. The only
| reason for anyone to not be a diehard supporters of the
| author's point of view is that they have mutilated minds
| and are coding bums.
| wsc981 wrote:
| _> Those who are in the right are able to form rational
| arguments, raise concerns about specific problems, and
| offer solutions. But no, not in this case. The only
| reason for anyone to not be a diehard supporters of the
| author 's point of view is that they have mutilated minds
| and are coding bums._
|
| Don't we take things a bit too serious these days? I've
| started programming with BASIC and I find the quotes
| kinda funny - I don't take it too serious and I am not
| sure if Dijkstra was being dead serious here either. For
| example: would anyone really think Dijkstra was in favour
| of locking COBOL teachers behind bars?
| rualca wrote:
| > Don't we take things a bit too serious these days?
|
| I agree that this self righteous fake outrage targeted at
| those who point out bullying and abuse gets a bit too
| tiring, and adds nothing to the discussion.
|
| This is a very good example. Calling out the lack of
| rational arguments or any reasoning regarding a technical
| subject, and resorting to abuse to fill the void with ad
| hominem and bullying, is expected to be addressed with
| explanations on the technical merits of the original
| proposals.
|
| But no, here we are wasting time with chatter that boils
| down to "why aren't you ok with being subjected to abuse?
| Don't we need to roll over and shut up when someone
| throws ad hominems?"
|
| I guess we don't? But it seems that some people whine
| when they can't take what they are dishing out.
| sieabahlpark wrote:
| > Programming languages should serve people, not the
| other way around.
|
| It's common to feel this way if you've never thought of
| computation as a field of theory instead of just using
| brew to install your compiler and being happy it works.
| [deleted]
| holoduke wrote:
| Dijkstra. A smart man. But also an arrogant prick. The
| typical professor living in his own bubble who never dealt
| with businesses and commercial activities. Those statements
| are totally nonsense.
| brokenkebab wrote:
| His statements make a lot of sense: the first language
| learned often has a profound effect on programming
| habits. That's why proverbs like "one can program in
| COBOL in any language" appeared.
| willtim wrote:
| When BASIC e.g. redefined what "variables" are and what
| the equals operator means, it _did_ cause a generation of
| programmers to drift away from the long established
| mathematical definitions. This persists to this day in
| languages like Python. I have had to explain to my
| daughter that Python variables and equals are not like
| those in her maths lessons. Dijkstra was right to call
| out the bad decisions that were made, although I do
| concede his methods of doing so were not ideal.
| tralarpa wrote:
| FORTRAN is seven years older than BASIC and uses the
| equal sign and variables in the same way. Here an excerpt
| from the 1957 paper by Backus et al.:
|
| "Evaluate the expression on the right of the = sign and
| make this the value of the variable on the left."
| willtim wrote:
| Good point, but IIRC Dijkstra didn't like Fortran either.
| ALGOL (1958) used ":=" for assignment, as did Pascal some
| years later.
| faichai wrote:
| Most of the current generation of programmers have been
| mentally mutilated by Object Orientation in the C++/Java/C#
| style.
|
| I include myself. It has been a definite struggle to
| onboard functional concepts and solve problems in a
| functional style. Just doesn't come naturally.
| rualca wrote:
| > Most of the current generation of programmers have been
| mentally mutilated by Object Orientation in the
| C++/Java/C# style.
|
| Why do you feel the need to denigrate people who are
| experienced in imperative and/or object-oriented
| paradigms?
|
| If there is any paradigm that holds its own value, why
| not praise the value it adds instead of resorting to
| nothing but ad hominems?
|
| I mean, if something was so unequivocally better then
| wouldn't this perhaps resulted in a massive adoption
| rate, and name-calling wouldn't pop up so frequently? But
| no, the popular thing to do is to just denigrate those
| who haven't jumped into that particular bandwagon.
| yakubin wrote:
| _> if something was so unequivocally better then wouldn
| 't this perhaps resulted in a massive adoption rate_
|
| That's not what we see happen in life. Just look at how
| dearly we hold on to coal plants. My country's energy is
| 90+% coal-based and there is an insurmountable opposition
| to atom, and great suspicion against solar (government
| recently started financially deterring against mounting
| solar panels).
| rualca wrote:
| > That's not what we see happen in life. Just look at how
| dearly we hold on to coal plants.
|
| What a non-sequitur. Adopting a programming paradigm in
| the code that we write on a daily basis has rigorously
| zero to do with the infrastructure cost of switching
| energy sources.
|
| This non sequitur about coal plants is even more absurd
| and ridiculous once we factor out the fact that the world
| is already moving away from coal to renewable energy
| sources, which is quite costly and resistant to change
| and subjected to an awful lot of special interest groups,
| and yet during that timeframe people still chose not to
| bother with functional programming fundamentalisms.
|
| If an idea is good then it holds its own against
| alternatives. Even in their pet projects people tend to
| not even bother with pure FP frameworks. While hundreds
| of millions of euros are being spent on wind farms,
| people like you and me don't even bother spending a few
| minutes to get a hello world going with Haskell, even
| though it's effortless and trivial. Why'd you think
| that's happenin?
| valenterry wrote:
| He's right though.
|
| Yeah, over a long time it will certainly happen that a
| good idea will manifest itself. But that process can take
| a long time, centuries even.
|
| Let's not forget that there are commercial interests for
| keeping certain languages down and pushing languages up.
| There are huge companies like Google, Facebook, ...
| pushing for languages (and frameworks).
|
| It's often easier to stick to something existing because
| change requires effort. And I think that's why coal was
| mentioned - it's easier to stick to it than to switch to
| something else, so it will take time but eventually it
| will happen.
| deepsun wrote:
| > Programming languages are just tools and as such, they
| should be reasonably easy to use.
|
| I'd like to argue on how reasonably simple is simple
| enough. If I need a carpenter to build a stairs I'm my
| house, I would expect a good carpenter to know and use all
| the tools appropriate, no matter how complex. Of course,
| most things are doable with just a hatchet just fine, but I
| d expect a professional to choose a hatchet because it's
| better, not because they never bothered to learn proper
| tools.
|
| So whenever a language puts "easy to learn" as their main
| feature, I see it as a guide "how to build stairs with just
| a hatchet".
| datatrashfire wrote:
| Ah yes, my brain has been permanently mutilated preventing
| me from reaching the one and only programmer nirvana found
| in functional programming. Give me a break.
| MaxBarraclough wrote:
| I don't know much Haskell, but isn't Haskell's goal to serve
| as a vehicle for research in functional programming
| languages, rather than to offer a language that's useful for
| real-world work?
|
| As I understand it, the core Haskell folks don't especially
| care about Haskell's (admittedly limited) real-world usage.
| Or, if you prefer: real-world usage is a side-effect, not a
| value.
| morelisp wrote:
| Conversely, I like that there's a language whose first
| priority is _not_ "how do I become economically useful to the
| media industry as fast as possible."
| _query wrote:
| > For instance, you don't see a "stand up a web app in
| Haskell in 10 minutes" tutorials. Historically it may have
| worked in Haskell's favour to slowly induct learners, but in
| this age where languages are fighting for mind space I don't
| agree that's a good approach anymore.
|
| There's now the IHP framework that allows you to build
| haskell web apps in like 10 minutes. If you're curious, check
| it out: https://www.youtube.com/watch?v=UbDtS_mUMpI It's been
| called "Haskell on Rails", and for good reason.
| uryga wrote:
| a detail that doesn't contradict anything you said but may be
| useful for someone unfamiliar with it:
|
| "avoid success at all costs" is usually meant to be parsed as
| "avoid [success at all costs]" i.e. don't compromise the
| language just to make it more popular.
|
| https://news.ycombinator.com/item?id=12056169
| ebingdom wrote:
| > I strongly dislike Haskell's focus on theory.
|
| First of all, I completely understand where you're coming from.
|
| But the fact that Haskell embraces computer science, unlike
| most other programming language communities, is what attracts
| me to it the most. I can geek out about the mathematics that
| leads to simpler, safer software with like-minded people. I am
| always learning from Haskellers more than from any other
| programmers.
|
| But it does take a lot of learning and unlearning to become a
| fluent functional programmer. This isn't because functional
| programming is more complicated than other paradigms (au
| contraire), it's because schools and universities have been
| mostly teaching OOP for the past 2-3 decades--unfortunately. As
| a result, most programmers have such a big gap in their
| knowledge that it can feel too daunting to dive in.
|
| We could definitely do a better job teaching the theory and
| explaining how it's useful. That would be better for everyone:
| better for curious minds who want to expand their programming
| skills, and better for me because I'd have more company to
| discuss it with.
| WastingMyTime89 wrote:
| > But it does take a lot of learning and unlearning to become
| a fluent functional programmer.
|
| Just no.
|
| The reason Haskell is so complicated to become fluent in has
| nothing to do with the inherent complexity of functional
| programming. The Haskell community is just in love with
| complexe abstraction for the sake of abstraction, extremely
| terse and hard to understand syntax (see for example the
| obsession with introducing convoluted operators) and
| generally favour hard to understand style like point free
| where pipes are a lot easier to follow. The Haskell community
| is full of people who are here to geek out rather than
| produce software. Haskell is what happen when you let one
| upmanship leads your language design.
|
| Meanwhile, you can learn SML, F# and Ocaml in a couple days,
| gradually enjoy the functionality they have to offer and
| benefit for a nice community. I really don't understand why
| anyone would choose Haskell.
| ebingdom wrote:
| > generally favour hard to understand style like point free
| where pipes are a lot easier to follow
|
| No they don't. Every Haskeller I know (myself included)
| acknowledges that sometimes point free style is better, and
| sometimes it isn't. None of them would unilaterally declare
| it better, and most generally avoid it except in very
| simple situations.
|
| > The Haskell community is full of people who are here to
| geek out rather than produce software.
|
| The Haskell community is full of people who are here to
| geek out about the best ways to produce software. This
| means they embrace tools like mathematics, and are
| generally extra thoughtful about structuring programs in
| principled ways. It's a wonderful community of people who
| care about the details of their craft and treat it like a
| skill to be honed over time. I have a lot of respect for
| that.
|
| If I could rant for a moment: I'm really sick of spending
| many years of my life investing in myself and my ability to
| produce software with the best tools humanity has to offer,
| only to have these people in Hacker News tell me I'm just
| doing mental masturbation when it seems like they don't
| even understand what they're criticizing.
| samhh wrote:
| Function application pipelines and function composition are
| closely related. Here's a "pipe" in Haskell:
|
| \x -> h $ g $ f x
|
| At a certain point you realise that in many cases seeing
| the `x` is not just useless but needless visual noise. The
| following is identical and more readable once you've
| internalised composition:
|
| h . g . f
|
| And this extends to a cute trick in which, if you need to
| explicitly provide that data to begin with, you can do
| this:
|
| h . g . f $ x
|
| I'm now working with Haskell having previously come from
| PHP and JS/TS. Composition is more readable, it's just
| harder to get started with because it's different to what
| you're used to.
|
| As for the "geek out" comment, of course, I enjoy
| programming for the intellectual sake of it. I'm not
| product-driven. Don't make the mistake of thinking
| everyone's the same as you, nor that of thinking there
| aren't benefits to being so intellectually-motivated. I
| don't know if it's intentional but your comment comes
| across as rather self-righteous.
| inimino wrote:
| If you don't understand why anyone would choose something
| that many people have chosen, you might want to be a little
| less bold in announcing what motivation those people must
| all have.
| WastingMyTime89 wrote:
| Oh but I know why they choose it. They want to geek out
| and feel smart.
|
| Haskell is somewhat unique regarding modern programming
| language in that it was created as a research project on
| programming language. Most other languages emerge as a
| solution to an engineering problem: C for Unix, Rust for
| Servo, Ocaml for Coq.
|
| If you allow me an analogy, a good programming language
| and its community starts wanting to build a great
| building and thinking their scaffolding technoly is
| inadequate. They then build better scaffolding as they
| are buildinh their building.
|
| Haskell started thinking they had a really cool way to
| build a scaffold and the community is still more
| interested in building scaffolding than actually using
| them. That's why there are more lense libraries than
| useful software built using Haskell.
| ebingdom wrote:
| > They want to geek out and feel smart.
|
| Jesus Christ, I am so sick of you and all the people like
| you who repeat this lie. I've invested a lot of time
| learning about category theory, domain theory, type
| theory, etc. so I could become the best version of myself
| as a programmer, and I have seen very real benefit from
| this investment. Only to have you and these other people
| in HN tell me I'm just trying to "feel smart". Your
| arrogance is so off-putting to me.
|
| When did HN become so anti-intellectual? If you don't
| understand something, then either learn it or don't--but
| you don't need to bash other people for their own
| efforts.
| WastingMyTime89 wrote:
| > Only to have you and these other people in HN tell me
| I'm just trying to "feel smart". Your arrogance is so
| off-putting to me.
|
| When did HN become so anti-intellectual? If you don't
| understand something, then either learn it or don't--but
| you don't need to bash other people for their own
| efforts.
|
| Listen, I have both an advanced degree in mathematics
| where I mostly did algebra and I have work for large
| software projects with hard constraints in the aerospace
| industry. I have nothing about people learning category
| theory. It's an interesting intellectual endeavour but
| people who think they are learning domain theory and
| category theory in order to be better engineers are just
| dicking around. It's not anti-intellectualism. It's a
| fundamental misunderstanding of what and where the
| problem actually is. Actually this last sentence sums up
| my issue with Haskell pretty well now that I think about
| it.
| tsimionescu wrote:
| Haskell embraces PLL. Computer science is much larger than
| PLL, and much of it ignores PLL entirely.
|
| In particular, most algorithms research and papers are
| expressed in imperative pseudo-code, as that is, in fact,
| much easier for humans to intuitively reason about than
| complex category theoretical concepts.
| ebingdom wrote:
| > much easier for humans to intuitively reason about than
| complex category theoretical concepts.
|
| We're not talking about any complex category theoretical
| concepts here. Functors, for example, are one of the first
| ideas one would learn in a category theory course. Likely
| even in the first lecture.
|
| Even the most advanced functional programs use only the
| most basic categorical constructs.
|
| And when you say "imperative pseudo-code", you've already
| conjured an implicit monad involving state, I/O, etc.
| Functional programming just teaches us that it's not the
| only one--and there are simpler ones that may be more
| appropriate for the given situation.
| tsimionescu wrote:
| > And when you say "imperative pseudo-code", you've
| already conjured an implicit monad involving state, I/O,
| etc. Functional programming just teaches us that it's not
| the only one--and there are simpler ones that may be more
| appropriate for the given situation.
|
| No, I have not. The fact that you can recreate state and
| I/O using monads does not mean that anyone using state is
| implictitly using a monad - monads have very specific
| properties that imperative code often doesn't have.
|
| It's only Haskell's laziness that makes it require monads
| for i/o, by the way. Other pure functional languages
| don't use them. For example, in Idris, IO and state are
| not monads, they are effects - tracked at the type system
| level, but without all of the commodity of monads. For
| example, you can have a single do-block in Idris that
| does IO and state operations without needing any monad
| transformers or lifting.
| ebingdom wrote:
| > monads have very specific properties that imperative
| code often doesn't have.
|
| You don't know what you're talking about. In the context
| of programming language theory, monads were first used by
| Eugenio Moggi precisely for the purpose of specifying the
| semantics of imperative programming languages. Only later
| did Wadler realize that they could be useful as user-
| defined constructs within a programming language.
|
| The "very specific properties" are actually just the
| identity and associativity laws of a certain monoid.
| These are trivially satisfied by imperative-style code
| including the "algorithms research and papers are
| expressed in imperative pseudo-code" you mentioned: you
| can write an imperative program that does nothing when
| sequenced with other programs (identity), and A; B; C
| does not require explicit grouping (associativity).
|
| > It's only Haskell's laziness that makes it require
| monads for i/o, by the way. Other pure functional
| languages don't use them. For example, in Idris, IO and
| state are not monads, they are effects - tracked at the
| type system level, but without all of the commodity of
| monads. For example, you can have a single do-block in
| Idris that does IO and state operations without needing
| any monad transformers or lifting.
|
| This is an incoherent train of thought. Haskell's
| laziness does not require the explicit use of monads, nor
| are monads only useful in the context of laziness.
| Haskell could have used Idris's IO system instead--
| algebraic effects work just fine in lazy languages. But
| also it is not true that Idris programs do not use monads
| just because you don't see the word "monad" in Idris
| programs. Effects give rise to a monad, mathematically;
| namely, the free monad for the algebraic theory. Read the
| seminal work by Plotkin and Pretnar, for example.
| 4ad wrote:
| Lazyness requires monads for IO, but monads are useful
| for much more than IO, even in eager languages.
| tsimionescu wrote:
| Absolutely, I meant "don't use them for IO", didn't mean
| to imply they are not used at all in other pure, strongly
| typed functional languages.
|
| I still think they don't often come up in algorithms
| research, though.
| ebingdom wrote:
| > I still think they don't often come up in algorithms
| research, though.
|
| Why are you so fixated on algorithms research in your
| attempts to dismiss the usefulness of functors and
| monads?
| tsimionescu wrote:
| You completely missed my purpose. The OP was claiming
| that Haskell is superior to some other programming
| languages because it "embraces computer science". I was
| pointing out that computer science is larger than PLL,
| and that much of computer science (algorithms research
| being a very productive field within CS) is not in fact
| using FP or PLL concepts to a large extent.
|
| Basically, I am only trying to dismiss the idea that
| Haskell is somehow closer to CS or more scientifically
| correct than other programming languages; instead, it is
| as related to CS as other languages are, just choosing to
| focus on a different area than many others.
|
| I also want to dismiss the idea that CS == FP, that
| imperative style reasoning is just some second-tier style
| not used in rigorous circles - much of the forefront of
| CS is indeed using imperative style constructs more than
| FP ones.
| samhh wrote:
| How can you say it's easier for humans to reason about when
| your test subjects almost all exclusively got started in
| programming with imperative programming? It's a slightly
| biased data set.
| pwdisswordfish8 wrote:
| The thing about learning from examples is that it's far too
| easy to learn the wrong thing from them, or nothing at all.
| When writing examples, one has to spend additional effort on
| explaining which parts of the example are 'moving parts' and
| which are 'fixed', i.e. which elements of the example are
| inherent to the problem you're solving, and which are just
| placeholders you should replace with your own. Many don't
| bother; it's why every Medium 'tutorial' basically reduces to
| 'here's a bunch of code you can copy-paste without
| understanding'.
|
| With a 'theoretical' description, like a BNF grammar or a Unix
| man page listing every possible option the program accepts,
| this is immediately obvious. Haskell's focus on 'theory'
| follows the same philosophy.
| tarsinge wrote:
| It's the same for me. I don't know Haskell yet but it reminds
| me the hard time I had with mathematics in school. It all
| changed when I started self-studying by first doing learning to
| do exercises and memorizing properties, and then learning the
| theory afterwards. For example I really enjoyed the first
| Andrew Ng machine learning course on Coursera, where you learn
| to do matrix calculations and backpropagation by hand for
| practical applications without much theory. It was great for
| building the intuition first.
| 3grdlurker wrote:
| In terms of learning I'm the exact opposite. I like to start
| with theory, then apply it into practice to affirm that the
| theory is correct (or that it isn't, and then make my own
| improvements). Not starting with theory feels too much like
| being directionless to me, or creating your own theory/mental
| model of what happened, which could be and is often wrong.
|
| In any case, all knowledge needs to be elevated to some level
| of theory because a correct theory guarantees the repetition of
| correct application.
| datatrashfire wrote:
| Look another nebulous post on functional programming.
| Osiris wrote:
| Let me see if I can sum this up...
|
| Functor = map
|
| Monad = flatMap
|
| Did I understand the article correctly?
|
| edit: this is an honest question. I way I read his explanation of
| Functor is that it's a function that implements map (list as
| input -> list as output).
|
| His explanation of Monad said that its an input of List, where
| each item returns it's own List, then the function returns a flat
| List of all the results.
| Dylan16807 wrote:
| That's the version for a list. But list is too simple to really
| see why people use these abstractions.
|
| flatMap doesn't let you implement the audit log example, or
| work on a binary tree, let alone try to implement something
| like IO.
| BoiledCabbage wrote:
| I have no idea why this is downvoted.
|
| For all the functor tutorials, it really is nothing more than
| the interface and rules that define map. And Monad is
| essentially flatmap.
|
| That's was so absurd about all of this.
| unanswered wrote:
| Please explain a parser monad in terms of flat map.
|
| To be concrete, I'm talking about the kind with the semantics
| that if `Parser X` parses an `X`, and `Parser Y` parses a
| `Y`, `>>` between them parses an X then a Y. I propose that
| you must explain that in terms of flatMap in order to say
| that this is a correct understanding of monads.
| BoiledCabbage wrote:
| Sure.
|
| flatMap() is a specific function that you already
| understand. It is the most common instance of a general fn
| pattern that is used for the specific data type Array.
|
| The flatMap equivalent function when used on the data type
| Promise is called then().
|
| The special function you are asking to understand ">>" is
| simply another instance of then(). This one still waits for
| the promise to complete - the only difference is that the
| lambda you pass to then() doesn't have access to the result
| of the previous promise.
|
| If you're using >>/flatMap on a Parser datatype, instead of
| running the lambda after first promise has completed
| successfully, it "runs" it after the first parser has
| completed successfully.
|
| flatMap and (all of its other data type specific names) are
| the essence of a Monad.
| dragonwriter wrote:
| > flatMap and (all of its other data type specific names)
| are the essence of a Monad.
|
| Well, the extra sauce on top of an Applicative Functor
| that makes a Monad; but the essence of an Applicative
| Functor is _also_ part of the essence of a Monad, and the
| essence of a plain Functor is part of the essence of an
| Applicative.
| Dylan16807 wrote:
| It's not "essentially flatmap" for more complicated data
| structures or data sources unless your definition of
| "flatten" is "do any arbitrary thing".
| bryan0 wrote:
| My definition of flatten would be: m m a -> m a
|
| Definitely not arbitrary but maybe not useful for many
| monads (??)
| wyager wrote:
| Yeah, in formal terminology that's "join". Join + return
| + monad laws is a perfectly reasonable definition of a
| monad.
| acomar wrote:
| you need fmap as well.
| Dylan16807 wrote:
| That's just a signature though, and the implementation
| could do _so many things_ for complex and /or strange
| data structures.
|
| So in a sense yes it's "just" this function that could do
| anything following the signature.
|
| But now you're in the same place you already were,
| looking at a simple type signature with massive hidden
| implications, and if you treat it like a list you'll miss
| those implications.
|
| "flatmap generalized to a vast field of dissimilar types"
| is much more complicated than just "flatmap"
| mrspeaker wrote:
| I think the downvotes are because it kind of sounds like you
| didn't read the article and are saying the things that the
| author directly states as a "misconception":
| "It is a common misconception that "monad is the same as
| flatmap", which has even made its way into some "monad
| libraries" in non-Haskell languages. flatmap is not "monad".
| flatmap is the particular implementation of the monad
| interface on lists. If you understand flatmap, you understand
| the monad implementation for the list datatype... you do not
| understand "monads" in general."
|
| Maybe you think the author is wrong, but your comment didn't
| read like you were addressing these points.
| _ph_ wrote:
| Or maybe the article isn't understandable. Sorry, I tried
| to read the article and just got confused and don't
| understand, what the article is trying to say. So it would
| be a good start to present the understanding one might get
| from that article and then get constructively corrected,
| where this understanding is wrong. In my eyes, this is the
| only way to understand a complex topic which isn't
| explainable in common language.
| BoiledCabbage wrote:
| So great call out. My mention of the downvotes wasn't in
| reference to my comment, but into the parent I was replying
| to who is clearly newish to Monads. And their summary is
| actually fairly accurate.
|
| If you can read and understand this comment
| (https://news.ycombinator.com/item?id=27639779) then you
| understand 80% of the practical use of Monad.
|
| And the majority of people who understand that comment,
| could now implement the flatMap/join/>>= for the "Maybe a"
| Monad. And could probably implement ">>" for it as well.
|
| And if you can implement "bind" for a randomly requested
| Monad, while you may not know what a Kleisli Category is,
| nor be able to define the monad laws and might still make a
| mistake implementing one, you have the essential concept of
| a Monad.
| avalys wrote:
| I feel like these tutorials always fall apart when they start
| introducing Haskell syntax and say "That's Monad, simple as
| that!" class Functor f where flip fmap ::
| f a -> (a -> b) -> f b class Monad m where (>>=)
| :: m a -> (a -> m b) -> m b
|
| Sorry, that's not doing much for me! I am now going to look for a
| tutorial on Monads that's written in, say, Python, so at least I
| don't have to learn an abstract new language alongside the
| abstract new concept.
| staticassertion wrote:
| I found this useful as a Rust dev:
| https://varkor.github.io/blog/2019/03/28/idiomatic-monads-in...
|
| I agree with you entirely that the Haskell syntax is probably
| one of the biggest issues with monads being approachable.
| wyager wrote:
| This blog post is maybe 10% about monads and 90% dealing with
| why you can't have them in rust (no HKTs). I hardly think
| that's more approachable...
| aszen wrote:
| Is that true though, you can't define a generic monadic
| interface yes but you can implement the same interface for
| your data types. That makes it less useful as the post says
| but still pretty powerful in some cases
| staticassertion wrote:
| It was for me.
| the_af wrote:
| What's difficult about Haskell syntax? Or do you mean "it's
| an issue because it doesn't look like C, Java or Python?".
| toastal wrote:
| In many ways Haskell (and other languages in the ML family)
| are LISP with a lot less parentheses.
| the_af wrote:
| In which ways?
|
| I don't find lisp syntax confusing either.
| magicalhippo wrote:
| For me it is indeed the latter. I can glean a lot of
| interesting stuff from other languages as long as they look
| a bit familiar. Haskell I usually come to a dead stop
| pretty quickly.
|
| I've tried to read up on some Haskell basics, but the main
| issue is I don't have any real incentive to use Haskell, I
| have other hobbies that are more tempting, so I forget
| pretty quick.
|
| The Java-ish version of the Functor interface for instance
| was quite readable to me, ugly or not.
| wyager wrote:
| > I am now going to look for a tutorial on Monads that's
| written in, say, Python
|
| It probably won't be very helpful. The definition of a monad is
| 90% in the types.
|
| People use Haskell to talk about monads for two reasons:
|
| 1. Its type system is expressive enough to represent them
| (parametric polymorphism and higher-kinded types)
|
| 2. It's one of a very small number of languages that doesn't
| let users wiggle their way around needing some kind of
| principled effect system, so you really can't get away without
| monads. Only in such an environment can the social pressure to
| take shortcuts be overcome, forcing people to structure their
| code carefully enough that the rich concept of monads isn't
| reduced to a few occasionally used and probably incorrectly
| implemented corner cases like optional values and maybe
| futures.
| jpcooper wrote:
| Uniqueness types are a way to get around monads.
| wyager wrote:
| Linear types let you safely do effect sequencing/mutation
| without monads, but I don't think it extends much beyond
| that.
| yoz-y wrote:
| Wait. Assuming one does not need or want to learn Haskell,
| what would then be a purpose of learning about monads?
|
| There seems to be a disproportionate amount of tutorials
| about monads. Not being able to provide sufficient amount of
| examples in other languages makes monads seem quite less
| useful. Maybe next article to write is "why should you learn
| about monads".
| morelisp wrote:
| There are two subtly different things here, "monads" as a
| concept and specific monads. If I ask you, what's the thing
| in common between async functions, nullable types,
| exception handling, sequenced evaluation, and arrays, and
| you only know, say, Java, Scheme, and Python - you'll
| probably not see anything.
|
| Nonetheless, these are all monads, and learning _specific_
| monads helps in lots of other languages. Optionals are nice
| in Java. Arrays in Lisps. Error handling in Scala. But the
| "universality" of monads escapes these languages. I would
| kill for better monadic error handling in Go, but
| conversely I would have little use for monadic I/O or
| futures.
|
| The critical mental step is seeing the commonality in all
| of these, which is really only _possible_ in languages at
| that level of type sophistication. And _necessary_ in
| Haskell where the particular combination of purity,
| laziness, and HKTs means monads are the only way decent way
| to do most of these. Once you see that commonality you can
| develop tools - patterns of mental reasoning about code and
| idioms for writing code - you can also use when dealing
| with the _specific_ monads or nearly-monads in other
| languages. But you can 't show that property in examples in
| a single other languages, and it's difficult to show how
| some deep structure is shared in examples spanning five
| languages.
| lmm wrote:
| Monads are a very effective technique for expressing custom
| requirements in your type system, e.g. "this function must
| be called only in a database transaction", "this function
| writes an audit log entry", "this function requires this
| level of authorization". The kind of thing you might think
| you would have to do via AOP/decorators/monkeypatching, you
| can usually do in plain old code (with all the attendant
| advantages) via a monad.
|
| If your type system can't express them then you lose most
| of the advantages and they're pretty pointless.
| calo_star wrote:
| I find this perspective intriguing, can you elaborate on
| that? For example why `m a -> (a -> m b) -> m b` but not
| something else?
| uryga wrote:
| > why `m a -> (a -> m b) -> m b` but not something else?
|
| it's basically continuation-passing-style (`a -> m b` is
| the "continuation"), you might as well ask "why can you
| represent so many control-flow things using CPS?". idk
| _why_ , but you can!
|
| from another angle, you could compare Monad with the less
| powerful Applicative. a formulation[0] that's easier to
| parse than the usual one[1] is: class
| Functor f => Applicative f where unit :: f ()
| pair :: f a -> f b -> f (a, b)
|
| if you're familiar with JS Promises, a rough analogy
| would be >>= (Monad) Promise.then
| pair (Applicative) [?] Promise.all unit
| (Applicative) = return (Monad) =
| Promise.resolve
|
| ignoring parallelism, you can implement Promise.all using
| Promise.then, but not the other way around.
|
| ---
|
| [0] Called "Monoidal" here:
| https://stackoverflow.com/q/45267953 [1] https://en.m.wik
| ibooks.org/wiki/Haskell/Applicative_functors...
| lmm wrote:
| Some people describe monads as "programmable semicolons"
| - they're pretty much the general concept of sequencing,
| "do this then do that", or indeed of imperative code. I
| don't think they're necessarily the ideal abstraction -
| something like ArrowChoice is "better" in a lot of cases
| - but in practice they seem to come up a hell of a lot,
| you can represent almost anything as a monad.
| rocqua wrote:
| Because so many types above are m of something. You as a
| programmer get to decide how that m type works, so you
| have a lot of control there.
| wyager wrote:
| Why monads and not something else? Because monads appear
| to be a/the mathematical structure underlying the
| denotation of programs with effects.
|
| Why that type signature? You only have two options: that,
| or m (m a) -> m a. The former is more popular for
| ergonomic reasons mostly. It doesn't really matter.
| alephu5 wrote:
| There are many examples of monadic patterns used in other
| languages, in particular to encapsulate failure or nullable
| values. I've also seen a lot of codebases that have
| implemented a custom "pipeline" class that can be composed
| together, such that some transformation or effect occurs
| every time a value passes from one pipeline to another.
| These are usually monads too, allowing for some minor
| deviations from the formal definition.
|
| Nevertheless language ecosystems outside Haskell, scala and
| F# don't recognise this as a general programming pattern,
| although given the current direction towards FP in the
| JavaScript and rust communities I think it's a matter of
| time.
| sfvisser wrote:
| Monads (and functors and applicative) are definitely useful
| outside of Haskell.
|
| At first glance monads seem less useful in languages with
| builtin mutable state, and freely allowing side effects.
| However, if you're writing somewhat more high-level domain
| specific code there are plenty of concepts that can be
| modeled using functors and monads very elegantly.
|
| Parser combinators, distributed data fetching, reactive
| data streams, composable tree traversals, etc
|
| I use them all the time in for TypeScript.
| _ph_ wrote:
| If monads exist outside of Haskell, why isn't there a
| text which explains them with out Haskell knowledge?
| Where would I find such a text and description?
|
| I would really like to understand what they are, but so
| far have not been able to find any explanation which
| tries to explain them in terms of english, math or some
| commonly known programming language.
| Huggernaut wrote:
| This is probably the best resource I've come across for
| discussing this subject:
| https://fsharpforfunandprofit.com/posts/elevated-world/
| _ph_ wrote:
| Thanks, but the article loses me at what this "elevated
| world" is. It doesn't even try to explain what it is and
| quickly goes over to F# syntax, which I don't understand.
| Why is there no explanation in the terms of English,
| math, and any common programming language? If Functors
| and Monads are real concepts, they should be describable
| in the terms listed above, if they are only describably
| in terms of functional languages, I would consider them a
| pure artifact of those languages :)
| pests wrote:
| There isn't one unique elevated world, as he goes on to
| say. Two he lists we are familiar with - options and
| lists. If you've ever used Option(Some|None) and pulled
| out a value from Some - the Option is the elevated world,
| the Some|None is the elevated value.
| samhh wrote:
| You have a container with `a` inside, let's call the
| container `m`. You have `m a`.
|
| You can provide a function with the type signature `a ->
| b`. With this we run the function underneath `m` and map
| `m a -> m b`.
|
| Concretely this might be with for example lists, where
| given a function `Int -> String` I can map `[Int] ->
| [String]`. What the functor abstraction gives you is a
| consistent, lawful way to define this phenomenon for
| virtually every type you'd ever want to "map". Likewise
| monads for "flat mapping".
|
| This explanation was 90% accurate. What's missing is that
| not every functor or monad is a "container", for example
| Haskell's `IO`, which rather represents an action of
| sorts to impurely get a value. But the intuition will
| build all the same if you play with it a bit. It's all in
| the type signatures.
| aszen wrote:
| There's a problem here as the author himself notes.
|
| I can provide you a simple example of a function with
| monadic interface, you will understand it any language
| but that will not actually help you to conceptualize the
| idea of monads and influence your thinking.
|
| You will just think that's simple, whats the fuss about
| it and miss the pattern altogether.
|
| Imagine asking the user to provide a number, in most
| languages you will get a `string | null` value and you
| wish to get a real number from it by parsing the string
| if the user entered it.
|
| In this case assuming you have a function `parseNo` that
| goes from `string -> int | null` you can create a special
| function which takes a `string | null` value along with
| the `parseNo` function and gives you back `int | null`. S
|
| Turns out that function can be defined for many different
| data types and it has special useful properties.
|
| In this case it allows us to compose functions neatly,
| without having to do a whole bunch of null checks.
|
| But in other scenarios it can do a lot more.
| _ph_ wrote:
| Sounds like you are describing high order functions as
| they are known from Lisp?
| aszen wrote:
| Yes higher order functions. A function that takes a
| function as an argument or returns a function. In lisp
| you can think of a higher order function as taking data,
| processing it and then returning it just like any other
| normal function because code is data.
| MauranKilom wrote:
| Say you have a source of A, and a way of turning an A
| into a source of B. An operation that uses these two
| inputs to produce a source of B is a monad.
|
| This is about 90% of the way there (and TFA actually
| contains very similarly phrased description, right before
| the monad definition you complained about).
|
| The problem is that English does not have the right words
| to describe the abstract idea behind "source", "way of"
| and "operation" precisely _and_ rigorously.
|
| Regarding "any common programming language": In a way,
| this is like trying to explain a concept like, say, "the
| adjoint operator" [0] to someone only aware of integers.
| Or trying to explain the idea of the Liskov Substitution
| Principle [1] to someone who just learned assembly.
|
| In each case above, the language used to describe the
| concept is _multiple_ layers of abstraction too low to
| succinctly ping down the concept in question. Haskell
| brings (or rather _is_ ) a very rich language suited
| specifically for accurately describing the elements
| involved. That's why 1) the concept of a Monad is
| visible/cleanly expressible in the first place, and 2)
| people use it to explain the concept, even when (like
| this blog post) not aiming at a Haskell (or Haskell
| learner) audience.
|
| Disclaimer: I'm just a lowly OOP programmer, not written
| a line of Lisp or Haskell in my life. Watching from the
| sidelines for now.
|
| [0]: https://en.wikipedia.org/wiki/Hermitian_adjoint
|
| [1]: https://en.wikipedia.org/wiki/Liskov_substitution_pr
| inciple
| _ph_ wrote:
| _Say you have a source of A, and a way of turning an A
| into a source of B. An operation that uses these two
| inputs to produce a source of B is a monad._
|
| Did you mean to write "Say you have a source of A, and a
| way of turning an A into a B. An operation that uses
| these two inputs to produce a source of B is a monad"?
|
| What exactly do you consider a "source"? And the way of
| turning an A into a B I would call a function. And
| combining the "source of A" with a function that converts
| A's into B's, a concationation or composition of
| functions, or a way of currying.
|
| And yes, I am fully aware, that you sometimes need to
| learn the vocabulary to have a minimum discussion. But
| considering I have a degree in math and CS, have learned
| at least 10 programming languages, especially Lisp with
| all of its contexts, I am highly suspicious about
| something that cannot expressed in plain language. But
| thanks, your explanation comes closest I have seen so
| far.
| MauranKilom wrote:
| > Did you mean to write "Say you have a source of A, and
| a way of turning an A into a B. An operation that uses
| these two inputs to produce a source of B is a monad"?
|
| No. That would be the Functor concept explained in TFA. A
| Monad is specifically what I wrote.
|
| > And combining the "source of A" with a function that
| converts A's into B's, a concationation or composition of
| functions, or a way of currying.
|
| Yes, pretty much. See the article (where one of the main
| points is specifically that the functor concept is not a
| highly complex thing. Just a name for a _kind_ of thing -
| an abstraction).
|
| > What exactly do you consider a "source"?
|
| That's exactly the point where the "genericness" of the
| concept (consider: it originates from category theory)
| precludes discussing it in _more_ specificity. It could
| be a Maybe <A> (std::optional in C++), it could be a
| List<A>, it could be a PotentialFutureUserInput<A> (aka
| IO in Haskell), it could be a (C#) Enumerable... Anything
| that "wraps" (in any sense) another type. A functor
| allows you to apply a function that transforms the inner
| type into another without leaving the wrapper. A monad
| allows you to apply a function to the inner type,
| transforming it into a different _now wrapped_ type, with
| the monad implementation taking care of "flattening" the
| wrappers. I will avoid the attempt to come up with an
| example, seeing as the article also criticizes the
| abundance of bad monad examples.
|
| Here, I've written a C++20 concepts version of Monad.
| Well, I hope I did. But even if correct, it's just not
| something C++ can express well, much less use well: Since
| functions are not first-class citizens, you can't spell
| "a function from A to M<B>" in a way the compiler can
| deduce. That's why you need the extra template argument F
| and corresponding constraint FAMB.
|
| https://godbolt.org/z/b3PoPYeE9
| loopz wrote:
| In most languages you will lack the Monad type and helper
| functions, and you'll have available many shortcuts that
| undermine efforts to set up monadic expressions.
|
| So something can behave similarly to a monad, but the
| type systems don't stop you self-sabotaging your code or
| misunderstanding good composition.
|
| Best way to learn is to learn Haskell deliberately.
| Better than Go, the language forces you to compose better
| code, and even learn the values of FP.
|
| If one is uninterested in learning CS or expanding one's
| toolbelt, it'll take some years until industry catches
| up.
| _ph_ wrote:
| So you are saying you cannot describe what a Monad is
| without the presense of a FP language?
| loopz wrote:
| I didn't say that. Learning Haskell may help the budding
| learner, though won't enforce the monadic laws.
| wyager wrote:
| The entire body of category theory is where all of this
| stuff comes from originally, not Haskell.
|
| > tries to explain them in terms of english, math
|
| There are a million English-language blog posts, and it's
| a mathematical concept. Just look up "monads are monoids
| in the category of endofunctors" if you want more of
| that...
| _ph_ wrote:
| I was looking for a simple explanation about what a Monad
| could be, not a mathematical theory.
| mrspeaker wrote:
| If you never want to learn Haskell (or PureScript or
| similar) then the author even says in the `If They're So
| Wonderful Why Aren't They In My Favorite Language?`
| section: "If you don't need IO
| sequencing, or purity because your language doesn't care
| about purity, or the list implementation's nondeterminism
| because that's a parlor trick, or STM because your
| ecosystem doesn't use it... there's not much point to
| worrying about it. Monad implementations in languages other
| than Haskell fail to take off because there generally isn't
| even a single thing that is improved by working with it
| through a monad interface, let alone a family of such
| things." "[...]It's definitely an interesting
| idea, and if you are a curious programmer, it's worth
| learning about this particular interface and what it can
| do. But it's not something you generally directly take back
| to your language"
| herbstein wrote:
| The point about Haskell is that it forces you to, or at
| least heavily nudges you to, understand monads and the rest
| of the typeclass hierarchy. However, you use the properties
| of monads all the time. The Stream APIs in JAVA and LINQ in
| C# are examples of abstractions built around Monads and
| their properties.
|
| The definition of a monad, as it relates to programming,
| emerges when you realize that an Option type, a
| Result/Either type, a List type, and a bunch of other non-
| container types all have the equivalent of a `flat_map`
| function. Write out a few mathematical rules for how a
| well-behaved implementation should compose and you have a
| Monad and its rules.
|
| This is why another approach to learning monads that isn't
| focused on the theory introduces it as a design pattern
| instead. I find this to be great for intuition but horrible
| for understanding personally, having used both approaches
| to teach people about Monads IRL.
| contravariant wrote:
| It also doesn't help that that definition removes pretty much
| every aspect of what makes a monad a monad. It also deviates
| quite a bit from the mathematical definition.
|
| A definition like: bind f (x:xs) = f(x)
|
| would fit this type signature, but would very much not be a
| monad.
|
| What makes a monad a monad is that it is a functor (so a
| special parametrized type of sorts) with
|
| - a function to wrap a single value (wrap :: a -> M a)
|
| - a function that can flatten nested versions (flatten :: M M a
| -> M a)
|
| - such that wrapping twice and flattening is the same as
| wrapping once
|
| - such that when flattening a triply nested structure it
| doesn't matter which level you flatten first.
|
| And of course it also needs to satisfy the functor axioms,
| which are also frequently overlooked, though they're not too
| difficult they just mean that 'fmap' needs to play nice with
| identity functions and function composition.
|
| And Haskell in particular suffers quite a bit from the fact
| that currying is a first class concept, while in category
| theory this is reserved for a rather special class of
| categories. This means that using functors in combination with
| functions of multiple arguments suddenly requires you to think
| about how to evaluate a tree of functions on a tree of values,
| where in most other languages you'd end up with a tree of
| tuples at worst. Granted you can do _more_ if you solve those
| problems, but it requires you to think about concepts that even
| category theorists consider pretty 'out there'.
| shp0ngle wrote:
| Well, that's Haskell for you.
|
| Haskell folks love arrows and one letter variable names.
|
| I don't know _why_ , I guess it's the algebra background of
| most Haskell folks.
|
| When I wrote some things in Haskell, I tried to use full words
| for function names and variable names, but it felt I am the
| only one. People just love to name stuff `a` `b` `m` `f` `k`
| creata wrote:
| The one-letter variables are all type parameters, and pretty
| much _every_ programming language uses one-letter variables
| for type parameters. And I can 't think of a better syntax
| for "functions from a to b" than (a - b). In a Java-ish
| syntax it'd look like: interface Functor {
| <T, U> Self<U> fmap(Self<T>, Function<T, U>); }
|
| which is just _torture_. What would it look like in your
| ideal syntax?
| one_comment wrote:
| Give the types useful names, like everything else. Recently
| I did a review for a new peer and was pleasantly surprised
| that they used proper names for generic arguments. At first
| I stumbled of course, because I wasn't used to generic
| arguments being longer than a few characters but I think it
| helped the readability a lot. interface
| Functor { <Source, Target> Self<Target>
| fmap(Self<Source>, Function<Source, Target>); }
|
| Same code, but you get an additional hint of what's going
| on.
| creata wrote:
| Alright, that's fine, but the person I replied to was
| complaining that "Haskell folks love arrows and one
| letter variable names", and my point was that _most_
| programmers love one letter variable names in this
| context.
| the_af wrote:
| > _Sorry, that 's not doing much for me!_
|
| It's easy if you first learn about Haskell notation and type
| classes, which pretty much every online tutorial covers. What
| you quoted isn't arcane, you're just unfamiliar with it (just
| like my granma would find C-like syntax illegible: she just
| wouldn't know anything about it).
| 0xFF69B4 wrote:
| Monads and Functors aren't particularly exciting in Python
| because properties or contexts can be expressed with e.g.
| boolean flags and properties or contexts can be ignored when
| applying functions to unrelated parts of an object.
| # milk_cows :: Person -> Tired Person def
| milk_cows(person): ... person["tired"] = True
| return person # feed_pigs :: Person -> Tired Person
| def feed_pigs(person): ... person["tired"] =
| True return person # birthday :: Person ->
| Person def birthday(person): person["age"] += 1
| return person # willy :: Person willy =
| {"age": 32, "tired": False} # fmap and >>=
| print(milk_cows(birthday(feed_pigs(willy))))
|
| In Haskell there would be a 'Tired a' type with a Monad
| instance to express the tiredness property and a Functor
| instance to apply functions to it with no impact on or
| knowledge of tiredness, e.g. aging.
| mrspeaker wrote:
| To be fair, this particular article is titled 'Functors and
| Monads For People Who Have Read Too Many "Tutorials"'... after
| your tenth monad tutorial, you start getting good at reading
| types!
| drdeca wrote:
| the first one means that flip fmap is a function which, given
| an "f a", and a function from a to b, will produce an "f b".
|
| for example, if "f" is "list of" and a is "integer" and b is
| "string", then flip fmap would take as input a list of integer,
| and a function from integer to string, and produce a list of
| string.
|
| If you've done some topology: [or, maybe a is a particular
| topological space and b is another topological space, and f is
| "the fundamental group of", then given an element of the
| fundamental group of a, and a function from the topological
| space a to the topological space b, it will give you a
| corresponding element of the fundamental group of b.]
|
| a and b are both types of things, f takes in a type, and
| outputs another type. so, a and f a are both types (usually
| different types). the thing is that f doesn't just let you make
| a type from another type, it also lets you translate a function
| from the type a to the type b, to a function from the type f a
| to the type f b.
| proc0 wrote:
| I suspect this is still not satisfying for people wanting to
| really know what a Monad is. Perhaps these simplified
| explanations can get away with explaining Functor (more so the
| "mechanics" of it) but it starts breaking down for Monad.
|
| I don't think there is a shortcut... these are highly abstract
| concepts.
|
| Abstraction is about exposing the most essential parts of
| something... Category Theory is perhaps the mathematics of
| abstract composition, exposing the most essential parts of how to
| combine (in the abstract). Monad and Functor arise from CT, and
| thus, are very abstract concepts, built from the axioms of an
| already abstract subject matter. This is why it's hard to
| "simplify" them any further. The formal definition of a Monad is
| itself the simplest way to expose it. The fastest way to grapple
| with it is through the axioms of CT, precisely because CT is
| arriving at this concept by abstraction; by only thinking about
| the most essential parts of it.
|
| That is probably not a satisfying explanation (to tell someone
| you need to learn axioms and laws before understanding this
| concept), but the alternative is to simplify and/or abstract
| something that is already as simple and as abstract as can be,
| albeit with esoteric names, and thus inevitably complicating it
| and shrouding the concept instead. i think the reason so many
| Monad tutorials aren't as satisfying is because they attempt to
| explain both a mathematical, abstract concept and also how it
| applies to programming, at the same time (without explicit
| separation). The latter is practical, the former is very
| abstract, and the bridge between them is very complex and usually
| left out, leaving the reader scratching their heads how and why
| you go from one to the other.
|
| It's worth mentioning Bartosz Milewski here (links below), as he
| has bridged this gap for many, but as mentioned, there is no
| shortcut.
|
| https://bartoszmilewski.com/
|
| https://www.youtube.com/channel/UC8BtBl8PNgd3vWKtm2yJ7aA
| siraben wrote:
| There was a particular moment I remember from Milewski's monad
| video, where likened monad tutorials to a situation where you
| arrive on a remote island and the islanders ask you to explain
| what a function is. One's first instinct might be to give a
| bunch of examples, for instance, you can have a function that
| takes a list of fishermen and order them by the number of
| fishes they caught, or a function to turn barley into beer,
| etc.
|
| Upon hearing this, the islanders think that functions must be
| some magical panacea, and so you backtrack on that and try to
| talk about functions as being like animals, they take input and
| produce output (good luck with function composition).
|
| The situation is like that with monads. They are far removed
| from the world of programming (the fact that there were
| connections at all to effects was remarkable!), but as such,
| you have to learn the ambient theory in which monads reside to
| really grok it.
| k__ wrote:
| The monads video by "Fun Fun Function" finally explained functors
| and monads for me in an understandable way.
| the__alchemist wrote:
| Abstractions (Functors, Monads, and otherwise) beg the question
| of "What value is this providing", in context of a specific
| problem. If the cognitive complexity added by an abstraction is
| worth the value it provides, I'll use it. If not, I won't.
|
| Example: I do mostly embedded prgramming, in Rust. My focus is on
| building working devices. I use abstractions and build APIs that
| are useful for solving specific problems I have now, or
| anticipate in the future. Eg an init function for an ADC that
| does the register writes described in the reference manual, so I
| can write `let adc = Adc::new()` instead of a series of reg
| writes.
|
| It's common in embedded rust programming to make heavy use of
| generics, traits etc, and other things that leverage the type
| system to make it so that as many mistakes are caught at compile
| time as possible.
|
| This sounds great in theory, but if a given abstraction makes
| type signatures a mess, doesn't allow me to configure a pin
| correctly due to an edge case, and provides no advantage if I QC
| I'm using the right pin, I won't use it. I see these Haskell
| examples as using abstractions by default, without questioning
| whether it's appropriate for a given use.
| adamnemecek wrote:
| The idea of "adjointness" is present like everywhere in math. I
| wrote up something on it https://github.com/adamnemecek/adjoint.
| smitty1e wrote:
| Brevity:
|
| class Monad m where (>>=) :: m a -> (a -> m b)
| -> m b
|
| Now, I am comfortable with upper case for type and lower case for
| instance.
|
| But m is an instance of a function, not a variable. But m, a, and
| b all look like variables. You are already hammering me with the
| brevity here.
|
| Maybe put the functions in a serif font, and variables in a sans-
| serif? Or use different colors? Other languages seem to be more
| helpful with the visual cues.
|
| Solving problems in SQL is a blast, so it seems like Haskell
| should be an easy lift, but the brevity of the syntax has sent me
| packing for years.
| dragonwriter wrote:
| > Now, I am comfortable with upper case for type and lower case
| for instance.
|
| Monad is a typeclass, not a type. m is a variable, whose value
| is a type constructor.
|
| > But m is an instance of a function, not a variable.
|
| On the type level, m is a variable representing a type
| constructor; a concrete value it might have is List. It is an
| instance of a _typeclass_ , not a function.
|
| > But m, a, and b all look like variables.
|
| m, a, and b are all type-level variables; the _value_ of m is a
| type constructor (which is essentially a type-level function),
| while the values of a and b are types.
| smitty1e wrote:
| My point is that keeping all that straight at the single
| letter level is brutal.
|
| If it were a physical building, Haskell would be brutalist.
| bmn__ wrote:
| > m, a, and b are all type-level variables; the _value_ of m
| is a type constructor (which is essentially a type-level
| function), while the values of a and b are types.
|
| Let's try to apply that knowledge, and use my tribe's creed
| that "similar things should look similar, and different
| things should look different": -- a, b
| types -- m type constructor (>>=) :: m a ->
| (a -> m b) -> m b -- a, b types -- m
| type constructor (>>=) :: m a -> (a -> m b) -> m b
|
| I think that's much better. GP's complaint was correct,
| writing different concepts with the same notation is
| confusing.
| smitty1e wrote:
| May good fortune land all over you.
| loopz wrote:
| There's alot of unspoken assumptions, like what m usually
| means. The brevity is supposed to make higher concepts
| expressible however. Monad m is just a basic building
| block.
|
| It's hard to find best intro material. The above could
| scare away someone else, even Haskellers.
|
| Maybe intro on how to read examples, explanations etc.
| could aide?
| thewakalix wrote:
| A response to the common proposal of "we should call functors
| Mappables instead": Names Do Not Transmit Meaning,
| https://www.parsonsmatt.org/2019/08/30/why_functor_doesnt_ma...
| epicureanideal wrote:
| This is a similar argument to not naming code directories based
| on anything reasonable, and naming them after types of fruit
| instead, because only by reading the code in each directory can
| you really understand it, and it always changes anyway.
|
| I think it's better to at least try to make a name that
| transmits partial meaning or a general gist of the idea.
| wyager wrote:
| > I think it's better to at least try to make a name that
| transmits partial meaning or a general gist of the idea.
|
| If we didn't use formal terms for math concepts,
| mathematicians would waste all their time arguing about the
| colloquial definitions of the colloquial words they tried to
| map onto abstract concepts. It's better to have a clean break
| and force people to learn the specific formal meaning rather
| than try to limp along on a busted half-intuition.
| acomar wrote:
| the terms actually do come from an intuitive place if you
| look at the underlying math. it's called a functor because
| it does something function-like - mapping arrows in the
| domain category to arrows in the codomain. in programming,
| we think of this as a higher-order function that lifts a
| function on a category (of types) to a function on a
| different category (of types). it's only when you try to
| elide that explanation that the name appears to come from
| nowhere.
|
| similarly, monads bear a strong resemblance to monoids - in
| fact, it's the same relationship as functions and functors
| and it's quite precise. these intuitions only become
| available to you with the math, though, and programmers
| have a strange aversion to mathematics for people who do it
| day in and day out.
| nicolasrusso wrote:
| I know this is just for Haskell but someone needs to start
| explaining category theory at a level 10 year olds can understand
| or something. Have struggled with confusion in the same way
| there.
|
| So far all I got from this is that functors are functions?
| dan-robertson wrote:
| I think category theory is just the wrong way to look at it. A
| category is quite an abstract thing and it takes a reasonable
| mathematical background just to have some good examples to work
| with (I don't really think type system categories are good to
| work with.)
|
| I prefer to just write down the type and stare at it and think
| about what sort of functions could have that type. Later you
| can look at the rules. And this mostly means one needs to have
| written a little (eg) Haskell so that the one can read simple
| types. The signature of fmap looks like: fmap
| :: (a -> b) -> functor a -> functor b
|
| It should easy to guess what sort of types and functions could
| satisfy that. The rule restricts the possibilities more:
| fmap (\x -> f (g x)) a == fmap f (fmap g a)
| chombier wrote:
| A functor is two things:
|
| - a parametrized type, like for instance List: you can have
| lists of integers, floats, functions, or any other type
|
| - a way of mapping (in the sense of List.map) between instances
| of the parametrized type, given regular functions. This mapping
| should behave nicely with respect to regular function
| composition.
|
| If your parametrized type is F, then the mapping operation
| turns ("lifts") regular functions A -> B into functions (F A)
| -> (F B) in a reasonable way: "if you give me a function then I
| can transform any list using it".
|
| Functors can be used to "decorate" values: you can model
| exceptions (some value or an error), promises (some value not
| yet computed), state (some value + side effects) and many other
| kinds of effects using functors.
|
| So in general an effectful computation will have type A -> (F
| B) for some functor F modeling the effect. And now you have a
| problem: how to compose effectful computations in order to
| build larger programs out of smaller ones? You cannot simply
| compose A -> (F B) with B -> (F C) since the types don't match.
|
| What you need is a way of transforming an effectful computation
| B -> (F C) into a lifted function (F B) -> (F C) so that you
| can compose A -> (F B) with (F B) -> (F C) in order to get A ->
| (F C).
|
| That device should satisfy some properties to make effectful
| composition work the way you expect (in particular there should
| exist identity effectful computations), and is called a monad.
|
| A monad is a functor + some extra operations (bind/return)
| satisfying properties that will make effectful computations
| (using this functor) composable.
|
| The type of bind is generally written (F A) -> (A -> F B) -> (F
| B) but really it is the same as (A -> F B) -> (F A -> F B)
| above (takes an effectful computation, gives back a lifted
| function).
| datatrashfire wrote:
| This was better than the tutorial, thank you.
| bruce343434 wrote:
| Clearer and more succinct than TFA, thank you
| rocqua wrote:
| > What you need is a way of transforming an effectful
| computation B -> (F C) into a lifted function (F B) -> (F C)
| so that you can compose A -> (F B) with (F B) -> (F C) in
| order to get A -> (F C).
|
| isn't this just applicative? What is needed to turn
| applicative into monad?
| chobytes wrote:
| I got a bit obsessed with this stuff (CT, HoTT, haskell, etc)
| and ended up studying math in college because of it. My advice
| is not to bother with CT. Its out of context, very abstract
| math, and you wont find any compelling examples or applications
| at any accessible level.
|
| I dont want to discourage you from learning math, but I think
| this isnt a good place spend too much thought at first.
| siraben wrote:
| As someone who studies both CS and pure math, it's quite
| interesting when I see posts that take a very operational reading
| of functors and monads, instead of its shorter and more precise
| categorical definition (as a mapping from one category to another
| that acts homomorphically over morphisms). That's not necessarily
| wrong, but it's an important thing to keep in mind when learning
| category theory through a programming context (e.g. Category
| Theory for Programmers by Milewski). When I tried to learn
| category theory in this way, past a certain point the
| abstractions felt too unnatural and arbitrary (e.g. lack of
| examples for adjunctions and free objects), whereas a good
| mathematical treatment of category theory would provide plenty of
| examples across different areas of mathematics such as algebra,
| topology and logic.
|
| I strongly agree with valenterry's comment[1] here, that post
| gives is an approximate definition of functors, even omitting the
| functor law. In particular, it is explaining what an
| _endofunctor_ is (in the context of FP, you mostly deal with one
| category anyway). Furthermore, even in Haskell, there is no such
| thing as the category of Haskell types[0] (because of things like
| divergence and partiality).
|
| For the curious, I've recommended Program Design By
| Calculation[2] to a number of people to show how category theory
| can be used as a powerful tool to reason about programs in an FP
| context while not compromising on the mathematics (the target
| audience is undergrad CS majors).
|
| EDIT: In particular, I have a bit of issue with this sentence
| after stating the signature of fmap, since it does not reference
| lawfulness.
|
| > _This is the full definition. There is no additional hidden
| machinery that Haskell is somehow automatically invoking when you
| "use a Functor"._
|
| [0] http://math.andrej.com/2016/08/06/hask-is-not-a-category/
|
| [1] https://news.ycombinator.com/item?id=27638565
|
| [2] https://www4.di.uminho.pt/~jno/ps/pdbc.pdf
| drdeca wrote:
| Do you happen to know of any programming language which has
| explicit support for multiple categories?
| hiker wrote:
| Lean[0]'s mathlib has quite nice formalisation of category
| theory[1] with plenty examples of concrete categories even
| sheaves and toposes.
|
| [0] https://leanprover.github.io/programming_in_lean/#01_Intr
| odu...
|
| [1] https://github.com/leanprover-
| community/mathlib/blob/master/...
| ebingdom wrote:
| > Furthermore, even in Haskell, there is no such thing as the
| category of Haskell types[0] (because of things like divergence
| and partiality).
|
| Divergence and partiality can easily be modeled by the category
| of complete partial orders and Scott-continuous maps. This is
| what you learn in, e.g., a course on domain theory and/or
| denotational semantics.
|
| Andrej Bauer is mainly arguing that Hask isn't a category
| because no one has formally specified it. For example, we would
| need to come up with a policy on when two arrows are considered
| equal, and it's not clear what that notion of equality should
| be.
| siraben wrote:
| Right, domain theory and denotational semantics let you talk
| about [?] in an order-theoretic setting, and I think it's
| also rich in connections between topology and lambda
| calculus. Though it might be the case that if functors
| already pose a pedagogical conundrum for programmers, Scott-
| continuity might be even harder to get across.
|
| A quote from Bauer's post is particularly relevant:
|
| > _[I am arguing against] the fact that some people find it
| acceptable to defend broken mathematics on the grounds that
| it is useful. Non-broken mathematics is also useful, as well
| as correct. Good engineers do not rationalize broken math by
| saying "life is tough"._
| catgary wrote:
| Brauer has clearly had a different experience with
| classical mechanics and engineering physics courses than I
| did...
| bcrosby95 wrote:
| Because most people suck at taking abstract mathematical
| concepts and seeing how they could apply to code. Including me.
| Let's take the definition of a function:
|
| > A function is a relation for which each value from the set
| the first components of the ordered pairs is associated with
| exactly one value from the set of second components of the
| ordered pair.
|
| Cool, I have no idea what the fuck this means and how it is
| useful to me.
|
| Oh wait, I use them every day.
| siraben wrote:
| That is the first definition stated in first-order logic,
| sure. But the operational reading of a (computable) function
| being something that takes in input and produces an output
| (via some algorithm) is still correct. It is possible to be
| both clear and correct with operational analogies. However,
| monads and functors don't readily admit an easy operational
| reading like functions.
| hiker wrote:
| > Cool, I have no idea what the fuck this means and how it is
| useful to me.
|
| > Oh wait, I use them every day.
|
| Do you really? Do the `functions` you use even fit the above
| definition?
|
| I bet you can think of infinite number of functions (bool ->
| bool). The above definition admits only 4 such functions.
| bb010g wrote:
| There are only 4 (2**2) such pure functions. For type (Bool
| -> IO Bool), there are infinite ([?]**2) such pure
| functions.
___________________________________________________________________
(page generated 2021-06-26 23:02 UTC)