Rendered at 23:42:39 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
lifis 10 hours ago [-]
If they want a dependently typed language, why not use one? Lean is good, and I don't think it has any significant downside wrt Haskell other than more limited library ecosystem (but I guess AI can translate Haskell libraries to Lean very effectively).
jhartikainen 13 hours ago [-]
Articles like this always make me wonder "there could be something interesting about this", but they always assume I know more math (or something) than I do.
Does anyone write about these kinds of topics in a more approachable manner, or is the math just so inherent to this, that I need to learn that first? (And if so, what do I need to read to learn that?)
I'll admit to only having read the first few chapters, but it came across as an approachable intro to the math.
seanhunter 10 hours ago [-]
He has also done a youtube series on category theory for programmers [1].
I have personally been using a book recommended by someone here called "Conceptual Mathematics"[2] and have been finding it great so far in the sense that it only relies on high-school level maths concepts while introducing the categorical concepts in contexts where you can see the value of the approach [3].
It is really an interesting set of ideas and as soon as you start using it, some things start to change in how you approach problems. Just today in fact I was working on something and thought to myself "I need the opposite of a forgetful functor - I wonder what that is?" (turns out it's the "free group functor"). But because I knew about the existence of the concept it helped turn what would otherwise have been a maths problem that I would have really struggled with into something that was conceptually easy for me to deal with. I essentially could use the category theory to turn a problem that was hard to think about into something easy to think about (and also to show that this was valid) and then I could solve the problem in a conventional way.
[3] As opposed to say Emily Riehl's "Category Theory in Context" https://math.jhu.edu/~eriehl/161/context.pdf where the maths required to even get started with the examples in the intro is graduate level. To get a sense, this is literally the first sentence of the first chapter: "A group extension of an abelian group H by an abelian group G consists of a group E
together with an inclusion of G ↪→ E as a normal subgroup and a surjective homomorphism E ->> H that displays H as the quotient group E/G. " It's pretty full on.
epolanski 13 hours ago [-]
Most of the functional programming-related math is very simple, especially when framing it/contextualizing it to programming (e.g. using data types instead of sets, interfaces instead of "algebras", etc).
There's two minor gotchas:
- functional programming's math is mostly rooted in algebra and category theory, whereas most math education from elementary through university focuses on analysis and calculus
- it is still math after all, and you're required to go step-by-step building definitions in a mathematical fashion. If you want to understand what a monad is, you need to understand what a functor is. To understand what a functor is, you need to understand what a type constructor (such as Array, List, Record or Option) and a map function are.
If you're interested, I have a (now archived) repository that explains the fundamentals of functional programming through TypeScript and fp-ts library, and it does so in a very strict mathematical, but very approachable regardless of education, fashion.
You could check the magma and semigroup paragraphs, they are short, to get a taste of it.
Obviously, it's math, so you're expected to learn those definitions (albeit they are very simple to understand and easy to remember) and practice it a bit by thinking/implementing a handful of magmas and semigroups, then moving on to concepts that build upon those (monoid, ordering, functor, etc) by adding more constraints.
I want to underline that fact that this stuff is not "read and move to the next chapter."
A lot of this kind of "machinery" in functional programming and category theory turns out to be essentially extremely abstract "superclasses" or "traits". In fact, many of them are too abstract to actually be defined in most programming languages. So they may appear as "design patterns" rather than actual definitions, if the language isn't quite expressive enough.
So to understand these ideas, you have to ask the questions:
1. Why this abstraction, and not a slightly different one?
2. What are some concrete examples of this abstraction? (Usually this feels like, "So, huh, familiar things X, Y and Z are all the same, viewed from this particular angle.")
3. What almost fits this abstraction, but not quite? And why?
4. Now that I see this pattern includes a whole bunch of interesting things, can I do anything useful with that?
A good example of (2) is realizing that futures, Rust's Result and Option, and Python's list/stream/etc compressions are almost the same thing, from a certain angle.
But it takes a while to collect all the related examples and to work through the connections carefully. The common patterns are usually really simple once you finally see them, which is part of the problem. They patterns are so abstract and cover so many things that it takes a while to work through the implications, and to decide if something is genuinely useful. Some very simple and widespread patterns will turn out to be boring, because they don't correspond to any problems you've ever seen.
crote 10 hours ago [-]
The main issue I kept running into with Haskell is that the machinery feels haphazardly implemented.
Sure, the concept of a monoid makes sense, but (at least a few years ago) why isn't a Haskell monoid defined as an extension of a semigroup? And even these days: where's magma?
What makes monads special enough to warrant its own first-class syntax and dozens of predefined instances, while dealing with a Sum or Product monoid feels like an afterthought?
If functors are as basic and fundamental as they seem, why do they get an awkward "fmap" - with most standard types re-exporting it as plain "map"?
Programming in Haskell felt like I was dealing with a random collection of category theory concepts thrown together in a weekend, rather than a mature and well-designed programming language. I have no doubt that there's some very solid reasoning behind it all, but the Haskell documentation does an awful job answering the questions you stated. And let's not even mention giving them programmer-friendly names rather than something a German mathematician chose 150 years ago...
mrkeen 10 hours ago [-]
Sometimes you go back and reinvent the stack (breaking everyone's code) and other times you introduce things non-breakingly.
> why isn't a Haskell monoid defined as an extension of a semigroup
class Semigroup a => Monoid a where
[https://hackage-content.haskell.org/package/base-4.22.0.0/docs/Data-Monoid.html]
> If functors are as basic and fundamental as they seem, why do they get an awkward "fmap" - with most standard types re-exporting it as plain "map"?
Anyway, it could be worse. You could be working in a language that can't even express the functor/map interface.
crote 8 hours ago [-]
> class Semigroup a => Monoid
Yes, hence the "at least a few years ago". But perhaps I'm misremembering and confusing it with applicative functors, it has been a few years.
> I guess they went with a non-breaking feature introduction.
Sure, but functors aren't exactly novel, are they? Although it has contributed a lot to programming language design in general, Haskell didn't exactly invent the concept of a generic "map". So why wasn't it there in the first place, and why was it - despite its obvious prominence - not deemed important enough for a (what seems to be tiny) breaking change?
> Anyway, it could be worse. You could be working in a language that can't even express the functor/map interface.
Are those still around? I thought even Go eventually started supporting generics?
mrkeen 4 hours ago [-]
"Generics" doesn't cut it. Most languages just can't functor.
"Awkward fmap" and "random collection of category theory concepts thrown together in a weekend" is great compared to the alternatives.
nextaccountic 10 hours ago [-]
There's a language that is Haskell-inspired but more mathematically correct (not by much mind you), and it's called Purescript. And of course it has the whole zoo of algebra in its stdlib. It also has a lot of interest type-level stuff including my all-time favorite, row types to represent things like Javascript objects that can have an unbounded number of fields (Typescript tries to approximate this but is less powerful).
The best decision ever Purescript made was to not make the language lazy by default. Laziness is still there if you want to opt in, but laziness by default is the mother of all leaks.
crote 8 hours ago [-]
I looked into Purescript for a frontend project, but it seemed to be a bit lacking in ecosystem at the time, so (despite its very limited type system) I went for Elm instead, in the hope that it'd grow into a more mature language. Quite a mistake that was...
internet_points 10 hours ago [-]
> thrown together in a weekend, rather than a mature
The fact that it wasn't thrown together in a weekend is the reason why we have both map and fmap, and why monoid wasn't originally an extension of semigroup (it is now). Haskell started out without that, then later added fmap and semigroup.
In any case, those are not real concerns when programming with Haskell. The actual concerns are more like "why do I have to wait for all this lens dependency stuff to compile just to fetch an url" or "why does the language server need so much ram"
crote 8 hours ago [-]
> In any case, those are not real concerns when programming with Haskell.
They were my concerns when learning Haskell - and I was following graduate-level courses at a university which contributes rather heavily to the Haskell ecosystem. They might not apply to you, but that doesn't mean they aren't real.
My limit was seeing a HTTP server being praised over and over again for it's wonderful type-safe API, trying to use it, and a few weeks in discovering that a very basic HTTP feature was considered an "open research question" and "would probably require a full redesign". Having poor compile time and consuming a crazy amount of memory was indeed also quite an annoyance. Having an entire ecosystem seemingly more concerned about writing theses about type theory than with building usable libraries? That was a dealbreaker.
Haskell is a great tool for programming language research, and I can't wait to see what kind of crazy things they'll end up doing with dependent typing. Unfortunately in my experience this also seems to make it into a rather poor choice for actual programming in a non-research environment.
I don't hate or dislike Haskell. If anything, I'm disappointed in it. I really want to love it and use it for everything, but it's just... not there yet.
nextaccountic 10 hours ago [-]
> A good example of (2) is realizing that futures, Rust's Result and Option, and Python's list/stream/etc compressions are almost the same thing, from a certain angle.
Why the "almost"? They are all monads (I suppose you meant list comprehensions)
One thing to note is that lists have two natural monads (kinda). One is the usual List monad in Haskell (which does the same thing as list comprehension), but the other is actually an almost monad like your point (3)! So Haskell solves this by defining the "other" monad with a newtype over lists (called ZipList), and actually gives it only an Applicative interface (which is like monad, but with a restricted comprehension - values can't depend on past values so it has no "memory")
This happens with integers in a more honest way, they have two "natural" implementations of monoids (actually infinite, but two stand out): you can add integers and you can multiply integers. In this case Haskell decided to not implement Monoid directly because one is not "more natural" than the other, but instead gives two newtypes, Sum and Product
ekidd 5 hours ago [-]
(Yes, I typed "comprehensions", but autocorrect is not my friend, I'm sad to say. Thanks for the correction!)
> Why the "almost"? They are all monads (I suppose you meant list comprehensions)
Sort of. In the right language, with the right implementation, any of these can be monads. In practice, JavaScript Promises aren't quite monads, and everything in Rust is a bit complicated because we have things like FnOnce vs Fn, and so on. And even when Rust has something that's conceptually a monad, you can't actually "impl Monad" for it, because the type system isn't quite expressive enough.
In general I think it's mistake to accidentally implement something that's not quite an algebraic or categorical structure. This often means that your design is just a bit "off" of a much cleaner design. But sometimes, like in Rust, you know why you can't use the clean mathematical structure.
Does anyone write about these kinds of topics in a more approachable manner, or is the math just so inherent to this, that I need to learn that first? (And if so, what do I need to read to learn that?)
I'll admit to only having read the first few chapters, but it came across as an approachable intro to the math.
It is really an interesting set of ideas and as soon as you start using it, some things start to change in how you approach problems. Just today in fact I was working on something and thought to myself "I need the opposite of a forgetful functor - I wonder what that is?" (turns out it's the "free group functor"). But because I knew about the existence of the concept it helped turn what would otherwise have been a maths problem that I would have really struggled with into something that was conceptually easy for me to deal with. I essentially could use the category theory to turn a problem that was hard to think about into something easy to think about (and also to show that this was valid) and then I could solve the problem in a conventional way.
[1] https://www.youtube.com/watch?v=I8LbkfSSR58&list=PLbgaMIhjbm...
[2] https://www.cambridge.org/highereducation/books/conceptual-m...
[3] As opposed to say Emily Riehl's "Category Theory in Context" https://math.jhu.edu/~eriehl/161/context.pdf where the maths required to even get started with the examples in the intro is graduate level. To get a sense, this is literally the first sentence of the first chapter: "A group extension of an abelian group H by an abelian group G consists of a group E together with an inclusion of G ↪→ E as a normal subgroup and a surjective homomorphism E ->> H that displays H as the quotient group E/G. " It's pretty full on.
There's two minor gotchas:
- functional programming's math is mostly rooted in algebra and category theory, whereas most math education from elementary through university focuses on analysis and calculus
- it is still math after all, and you're required to go step-by-step building definitions in a mathematical fashion. If you want to understand what a monad is, you need to understand what a functor is. To understand what a functor is, you need to understand what a type constructor (such as Array, List, Record or Option) and a map function are.
If you're interested, I have a (now archived) repository that explains the fundamentals of functional programming through TypeScript and fp-ts library, and it does so in a very strict mathematical, but very approachable regardless of education, fashion.
You could check the magma and semigroup paragraphs, they are short, to get a taste of it.
Obviously, it's math, so you're expected to learn those definitions (albeit they are very simple to understand and easy to remember) and practice it a bit by thinking/implementing a handful of magmas and semigroups, then moving on to concepts that build upon those (monoid, ordering, functor, etc) by adding more constraints.
It's not read and move to the next chapter.
https://github.com/enricopolanski/functional-programming
A lot of this kind of "machinery" in functional programming and category theory turns out to be essentially extremely abstract "superclasses" or "traits". In fact, many of them are too abstract to actually be defined in most programming languages. So they may appear as "design patterns" rather than actual definitions, if the language isn't quite expressive enough.
So to understand these ideas, you have to ask the questions:
1. Why this abstraction, and not a slightly different one?
2. What are some concrete examples of this abstraction? (Usually this feels like, "So, huh, familiar things X, Y and Z are all the same, viewed from this particular angle.")
3. What almost fits this abstraction, but not quite? And why?
4. Now that I see this pattern includes a whole bunch of interesting things, can I do anything useful with that?
A good example of (2) is realizing that futures, Rust's Result and Option, and Python's list/stream/etc compressions are almost the same thing, from a certain angle.
But it takes a while to collect all the related examples and to work through the connections carefully. The common patterns are usually really simple once you finally see them, which is part of the problem. They patterns are so abstract and cover so many things that it takes a while to work through the implications, and to decide if something is genuinely useful. Some very simple and widespread patterns will turn out to be boring, because they don't correspond to any problems you've ever seen.
Sure, the concept of a monoid makes sense, but (at least a few years ago) why isn't a Haskell monoid defined as an extension of a semigroup? And even these days: where's magma?
What makes monads special enough to warrant its own first-class syntax and dozens of predefined instances, while dealing with a Sum or Product monoid feels like an afterthought?
If functors are as basic and fundamental as they seem, why do they get an awkward "fmap" - with most standard types re-exporting it as plain "map"?
Programming in Haskell felt like I was dealing with a random collection of category theory concepts thrown together in a weekend, rather than a mature and well-designed programming language. I have no doubt that there's some very solid reasoning behind it all, but the Haskell documentation does an awful job answering the questions you stated. And let's not even mention giving them programmer-friendly names rather than something a German mathematician chose 150 years ago...
> why isn't a Haskell monoid defined as an extension of a semigroup
> If functors are as basic and fundamental as they seem, why do they get an awkward "fmap" - with most standard types re-exporting it as plain "map"?map was in the original 1990 Haskell spec [https://www.altocumulus.org/haskell-report-1.0.pdf]. Functors & fmap came later. I guess they went with a non-breaking feature introduction.
Other times they do a breaking change. Monads didn't used to need to be Applicative Functors. Now they do. [https://wiki.haskell.org/Functor-Applicative-Monad_Proposal]
Anyway, it could be worse. You could be working in a language that can't even express the functor/map interface.
Yes, hence the "at least a few years ago". But perhaps I'm misremembering and confusing it with applicative functors, it has been a few years.
> I guess they went with a non-breaking feature introduction.
Sure, but functors aren't exactly novel, are they? Although it has contributed a lot to programming language design in general, Haskell didn't exactly invent the concept of a generic "map". So why wasn't it there in the first place, and why was it - despite its obvious prominence - not deemed important enough for a (what seems to be tiny) breaking change?
> Anyway, it could be worse. You could be working in a language that can't even express the functor/map interface.
Are those still around? I thought even Go eventually started supporting generics?
Go - no. https://www.reddit.com/r/golang/comments/v1ljep/monads_and_p...
Swift - no. https://forums.swift.org/t/higher-kinded-types-monads-functo...
Rust - no. https://www.reddit.com/r/rust/comments/152bgj0/current_state...
"Awkward fmap" and "random collection of category theory concepts thrown together in a weekend" is great compared to the alternatives.
The best decision ever Purescript made was to not make the language lazy by default. Laziness is still there if you want to opt in, but laziness by default is the mother of all leaks.
The fact that it wasn't thrown together in a weekend is the reason why we have both map and fmap, and why monoid wasn't originally an extension of semigroup (it is now). Haskell started out without that, then later added fmap and semigroup.
In any case, those are not real concerns when programming with Haskell. The actual concerns are more like "why do I have to wait for all this lens dependency stuff to compile just to fetch an url" or "why does the language server need so much ram"
They were my concerns when learning Haskell - and I was following graduate-level courses at a university which contributes rather heavily to the Haskell ecosystem. They might not apply to you, but that doesn't mean they aren't real.
My limit was seeing a HTTP server being praised over and over again for it's wonderful type-safe API, trying to use it, and a few weeks in discovering that a very basic HTTP feature was considered an "open research question" and "would probably require a full redesign". Having poor compile time and consuming a crazy amount of memory was indeed also quite an annoyance. Having an entire ecosystem seemingly more concerned about writing theses about type theory than with building usable libraries? That was a dealbreaker.
Haskell is a great tool for programming language research, and I can't wait to see what kind of crazy things they'll end up doing with dependent typing. Unfortunately in my experience this also seems to make it into a rather poor choice for actual programming in a non-research environment.
I don't hate or dislike Haskell. If anything, I'm disappointed in it. I really want to love it and use it for everything, but it's just... not there yet.
Why the "almost"? They are all monads (I suppose you meant list comprehensions)
One thing to note is that lists have two natural monads (kinda). One is the usual List monad in Haskell (which does the same thing as list comprehension), but the other is actually an almost monad like your point (3)! So Haskell solves this by defining the "other" monad with a newtype over lists (called ZipList), and actually gives it only an Applicative interface (which is like monad, but with a restricted comprehension - values can't depend on past values so it has no "memory")
This happens with integers in a more honest way, they have two "natural" implementations of monoids (actually infinite, but two stand out): you can add integers and you can multiply integers. In this case Haskell decided to not implement Monoid directly because one is not "more natural" than the other, but instead gives two newtypes, Sum and Product
> Why the "almost"? They are all monads (I suppose you meant list comprehensions)
Sort of. In the right language, with the right implementation, any of these can be monads. In practice, JavaScript Promises aren't quite monads, and everything in Rust is a bit complicated because we have things like FnOnce vs Fn, and so on. And even when Rust has something that's conceptually a monad, you can't actually "impl Monad" for it, because the type system isn't quite expressive enough.
In general I think it's mistake to accidentally implement something that's not quite an algebraic or categorical structure. This often means that your design is just a bit "off" of a much cleaner design. But sometimes, like in Rust, you know why you can't use the clean mathematical structure.