Live data from Hacker News

Functors, Applicatives, and Monads

thecoder.cafe

81–90 of 90 posts

Re: Functors, Applicatives, and Monads

#81
post #75

Earlier quoted context omitted.

> Why would they need a static type system in 2030? Why do many people talk about type systems as if they're only a safety guard? To me that's never the main role of type systems. I don't know what's the word for it, but types allow me to read the code, on a high level. Sure AI will write them but as long as software engineers exist, we still have to read the code. How do you even read code without types? Comments? U…

> How do you even read code without types? We're not going to settle the preference for dynamic vs static types here. Its probably older than both of us, with many fine programmers on both sides of the fence. I'll leave it at this: well-informed programmers choosing to write in dynamically typed languages DO read code without types, and have happily done so since the late 1950s (lisp). The funny thing is, I experienc…

> The funny thing is, I experience the same "how do you even??" feeling reading statically typed code. There's so much... noise on the screen, how can you even follow what's going on with the code? I guess people are just different?

That really depends on the language, though. If you have type inference, you don't really have to write down that many types, e.g. it's in theory possible entire Haskell programs without mentioning a single type - though in practice, nobody does that for larger programs.

> Remember that dynamic languages ARE usually typed, they are just type checked at runtime not compile time.

This is a common misconception. Dynamically typed languages are not type checked at all. The only thing they'll do is throw errors at runtime in specific situations, whereas statically typed languages verify that the types are correct for every possible execution (well, there are always some escape hatches, but you have to know what you're doing). In a dynamically typed languages it's e.g. perfectly possible to introduce a minor change somewhere that will suddenly cause a function very far removed to break, e.g. if you suddenly return a null value where none was expected, something which I've experienced a ton when working on Ruby codebases (tbf, this can also happen in any statically typed language that adjoins "null" to every type - but there's plenty of modern languages like Rust, Swift, Kotlin, Scala etc. that fix this oversight). If you do that in a modern statically typed language, the compiler will yell at you immediately.

> The errors I'm talking about are like "this CSS causes the element to draw part of its content off-screen, when it probably shouldn't". In theory, some sufficiently advanced type system could catch that (and not catch elements off screen that you want off-screen)? But realistically: pretty challenging for a static type system to catch

I'm perfectly willing to believe that type systems are not very helpful for those kinds of tasks. I think type systems are definitely more useful when there's complex business logic involved, e.g. in huge, complicated backends with many different sorts of entities.

Re: Functors, Applicatives, and Monads

#82
post #4

Unfortunately, while you may not have appreciated the tone of the Haskell interaction, they are correct in their assessment from a factual perspective. This explanation propagates a number of misunderstandings of the topics well known to be endemic to beginners. In particular, I observed the common belief that functors apply to "containers", when in fact they apply to things that are not containers as well, most nota…

In general, in abstract mathematics no analogy or "intuitive concept" of something will ever replace the rigorous definition. That doesn't mean that imperfect analogies can't be useful, though. You just have to use them as a starting point instead of stopping there.

I think the container analogy can be useful up to a point. There is (potentially) something of value wrapped in another type (e.g. an integer "wrapped in" IO) and we usually cannot access it directly (because of various reasons: because IO is special, because a list may be empty, etc.), but we can string together some operations that manipulate the contents implicitly.

Re: Functors, Applicatives, and Monads

#83
post #4

Unfortunately, while you may not have appreciated the tone of the Haskell interaction, they are correct in their assessment from a factual perspective. This explanation propagates a number of misunderstandings of the topics well known to be endemic to beginners. In particular, I observed the common belief that functors apply to "containers", when in fact they apply to things that are not containers as well, most nota…

I agree. The "container" intuition for Monads leaves you stuck when you try to contemplate IO (or even Promises, these days), because the "bind" operator looks like it does something impossible: extract "the" `a` from the `IO a`, when you have no idea what it is. (Trust me, I spent a long time stuck at this point.) Better to think of Monad as "Applicative + join" (you need Applicative to get `pure`). If you think of…

I prefer the "join" approach for beginners too, but >>= has become so pervasive that I feel bad trying to explain it that way. Turning people loose on monad-heavy code with that approach still leaves them needing to convert their understanding into >>= anyhow.

One does wonder about the alternate world where that was the primary way people interacted with it.

Re: Functors, Applicatives, and Monads

#84

Earlier quoted context omitted.

I'm really focusing less on the idea that Dict the data type with it's associated methods is like a function, and more on the idea that a dictionary in the general sense is a mapping of input values to output values, and you can think of functions that way. That said, there are some pretty reasonable analogies to be made between common dictionary operations and functions. For example, adding and removing items can be…

> and more on the idea that a dictionary in the general sense is a mapping of input values to output values, and you can think of functions that way. So what's the difference between a map and a dictionary then? > Here's a really short example I put together Much appreciated. I don't really know Haskell (nor any other functional language), but I'm pretty sure I understood it. > This particular representation of dicti…

> So what's the difference between a map and a dictionary then?

You're asking good questions and catching me being imprecise with my language. Let me try to explain what I'm thinking about more precisely without (hopefully) getting too formal.

When I say "a function is a mapping of values" I'm really trying to convey the idea of mathematical functions in the "value goes in, value comes out" sense. In a pure function, the same input always returns the same output. If you have a finite number of inputs, you could simply replace your function with a lookup table and it would behave the same way.

When I talk about dictionaries, I'm speaking a little loosely and sometimes I'm taking about particular values (or instances of a python Dict), and other times I'm being more abstract. In any case though, I'm generally trying to get across the idea that you have a similar relationship where for any key (input) you get a particular output.

(aside: Literally right now as I'm typing this comment, I also realize I've been implicitly assuming that I'm talking about an _immutable_ value, and I've been remiss in not mentioning that. I just want to say that I really appreciate this thread because, if nothing else, I'm going to edit my blog post to make that more clear.)

The main point is that dictionaries are made up of discrete keys and have, in Python at least, a finite number of keys. Neither of those constraints necessarily apply to functions, so we end up in an "all dictionaries are functions, but not all functions are dictionaries" situation.

> Yeah that's pretty much what I had in mind, and yes it's possible but it feels forced. For one you're not actually removing an element, you just make it impossible to retrieve. A distinction that might seem moot until you try to use it, depending on the compiler magic available.

This is a great example of the kind of thinking I'm trying to address in the article. You're completely right in a very mechanical "this is what memory is doing in the computer" sort of sense, but from the standpoint of reasoning about the problem space deleting an element and being unable to access the element are the same thing.

Of course in the real world we can't completely handwave away how much memory our program uses, or the fact that a function encoding of a dictionary turns a constant time lookup into a linear time lookup. Those are real concerns that you have to deal with for non-trival applications, even in a pure functional language.

The benefit you get, and I apologize because this is hard to explain- let alone prove, is that you can often end up with a much _better_ solution to problems when you start by handwaving away those details. It opens up the solution space to you. Transformations to your architecture and the way you think about your program can be applied regardless of the specific representation, and it's a really powerful way to think about programming in general.

Re: Functors, Applicatives, and Monads

#85

Earlier quoted context omitted.

> A function a->b is a container for bs Anecdotally, this is one of those things that's trivially true to some people, but really hard for other people to internalize. I think it's why the "container" can lead people astray- if you haven't internalized the idea of functions as being indexed by their argument, it's a really mind twisting thing to try to make that leap.

I can recommend learnung some scala, where HashMap extends PartialFunction

I've never looked at scala, but that's really interesting. Do you find that's useful in practice?

Re: Functors, Applicatives, and Monads

#86

Earlier quoted context omitted.

> and more on the idea that a dictionary in the general sense is a mapping of input values to output values, and you can think of functions that way. So what's the difference between a map and a dictionary then? > Here's a really short example I put together Much appreciated. I don't really know Haskell (nor any other functional language), but I'm pretty sure I understood it. > This particular representation of dicti…

> So what's the difference between a map and a dictionary then? You're asking good questions and catching me being imprecise with my language. Let me try to explain what I'm thinking about more precisely without (hopefully) getting too formal. When I say "a function is a mapping of values" I'm really trying to convey the idea of mathematical functions in the "value goes in, value comes out" sense. In a pure function,…

Thanks for the detailed responses, highly appreciated.

I taught myself programming as a kid using QBasic, and quickly moved on to Turbo Pascal and assembly, so clearly my programming career was doomed from the start[1].

For one I do like to keep in mind how it will actually be executed. The times I've not done that it has usually come back to bite me. But that perhaps hampers me a bit when reading very abstract work.

That said I like going outside my comfortable box, as I often find useful things even though they might not be directly applicable to what I normally do. Like you say, often changing the point of view can help alot, something that can often be done in a general way.

Anyway, looking forward to the rest of the article series and the talk.

[1]: https://en.wikiquote.org/wiki/Edsger_W._Dijkstra#How_do_we_t...

Re: Functors, Applicatives, and Monads

#87

Earlier quoted context omitted.

I'm not familiar with Haskell and am really, really struggling to follow the article. In the case of the functor, the author doesn't explain in technical, specific enough terms the difference between "open the box, extract the value out of it, apply the function, and put the result back in a box" and "apply a function to a box directly; no need to perform all the steps ourselves." I have no idea what 'apply a functio…

The distinction is that in general “opening a box and extracting the value” makes no sense, as it's not a thing that can be done in general. If your box is a Maybe, there might not be a value to extract. If it's a list, there might be zero or multiple values. It only ever makes sense to map over the contents of the box, replacing the values with their image under the map.

[deleted]

Re: Functors, Applicatives, and Monads

#88
post #83

Earlier quoted context omitted.

I agree. The "container" intuition for Monads leaves you stuck when you try to contemplate IO (or even Promises, these days), because the "bind" operator looks like it does something impossible: extract "the" `a` from the `IO a`, when you have no idea what it is. (Trust me, I spent a long time stuck at this point.) Better to think of Monad as "Applicative + join" (you need Applicative to get `pure`). If you think of…

I prefer the "join" approach for beginners too, but >>= has become so pervasive that I feel bad trying to explain it that way. Turning people loose on monad-heavy code with that approach still leaves them needing to convert their understanding into >>= anyhow. One does wonder about the alternate world where that was the primary way people interacted with it.

I think you don't have to teach people to program with `join`, but just that `m >>= f = join (fmap f) m`. It explains away the "get a value out" question but teaches the most common function from the interface.

Re: Functors, Applicatives, and Monads

#89
post #37
post #22

Earlier quoted context omitted.

Functions are just containers of calculations (the whole “code is data”). I don’t know why lists as values in a container would be confusing. Lots of very popular languages literally have box types which may not be exactly the same, but show that expecting containers to potentially commission complex data isn’t unusual.

> I don’t know why lists as values in a container would be confusing. The GP makes it pretty clear - the misunderstanding is that there is one value in a container. A list has many.

That's like saying a dev would be confused that an Object can contain a list. I can't see that tripping up anyone but the most junior of developers.

Re: Functors, Applicatives, and Monads

#90
Monads in Haskell is just a way to combine two functions into one when the output of the first function doesn't match the input of the 2nd function.

That's all there is to it.

It is not a way to "box values". Yes you can use it for that if the functions you combine happens to operate on "boxed values" (like Maybe) but that has nothing to do with the fundamental idea of what a monad is.

And yes there are "monad rules" that ensures that combining the functions "makes sense". But people sometimes use monads in Haskell that doesn't actually follow those rules. But it works fine anyways for whatever they are doing.

Post reply on HN