The more of Haskell's ideas that bleed into mainstream languages, the happier the world will become. It is just so beautiful how these higher abstractions can become so useful and powerful for tying all kinds of things together.
thomasfromcdnjs 10 hours ago [-]
I've been vibe coding my own language and it's compiler is in Haskell, it's been great for the agents to be inspired by Haskell and I'm having fun just adding random language features, currently implementing prophecy ghost variables. Not really a public project but can suss it out for now at http://lsalpha.com
bigmadshoe 10 hours ago [-]
This is super cool! You should put a clear example implementation of a feed forward network, transformer block, loss function, or something more ML specific on the homepage, assuming you're primarily targeting that domain.
EDIT: also the links to GitHub on https://lsalpha.com/systems are 404ing for me, so I exited your site without an example of what a real implementation might look like.
entropy47 8 hours ago [-]
Neatly summarises my experience of most vibe coded projects unfortunately.
fourthark 3 hours ago [-]
Presumably the repo is private...
10 hours ago [-]
i2talics 10 hours ago [-]
I've long suspected that one of the things that makes monads notoriously difficult to explain to people outside of the Haskell ecosystem is that very few other languages even have a kind system that you are allowed to manipulate in code (and in some, like C++, it's so esoteric that you basically never see it). First explaining that you can even abstract over "type->type" kinded things gets you a lot of the way towards monads without actually describing monads.
senshan 6 hours ago [-]
The problem is not monads. Any programmer understands the need to represent "the world". It is the need to keep that representation modular that leads to monad transformers, which makes regular people (like me) simply check out and go back to C++
Monads have no inherent connection to any notion of "the world"; that's just a thing for Haskell's IO monad specifically (and perhaps derivatives thereof) because what that abstracts over is effects that are external to the program.
A monad is any construct of abstraction that obeys the Monad Laws. This may seem a useless definition, but my point is that deriving a useful intuition for the concept of the monad is necessarily challenging because they cannot readily be boiled down any further than that without loss of information.
The best intuitive explanation I've developed so far is that a monad is an abstraction over structures of ordered computations. The most ready examples for non-functional programmers are probably exceptions (and their handlers) and nullable types (typically called "optional types" in languages with first-class handling of such things, unlike Java).
taolson 9 hours ago [-]
That may be true for functions which operate on a generic Monad typeclass, but typeclasses (Higher kinded types) aren't required to implement concrete Functor / Applicative / Monad instances for a particular type, e.g. Maybe:
fmap f Nothing = Nothing
fmap f (Just x) = Just (f x)
pure x = Just x
Nothing >>= f = Nothing
Just x >>= f = f x
mrkeen 3 hours ago [-]
"Generic" and "for a particular type" cancel out.
You don't need List<A> to write an IntList, nor do you need + to work on different integers in order to have a particular integer.
fn-mote 8 hours ago [-]
When people say they don’t understand Monad, my first assumption is that they are wearing their ignorance as a badge of honor.
Otherwise memorize a little bit and dispose of the mystery. It’s not that far out.
lolakutty 3 hours ago [-]
It seems that in java you could write `ArrayList<A>`. I think this type is actually of kind "type -> type". But in java it seems that we cannot have the type `A<B>`, and functors and monads require that..
mrkeen 3 hours ago [-]
Just to polish that,
ArrayList's kind is type->type
ArrayList<A>'s kind is type.
The A is what plugs into type on the LHS of the arrow, leaving only the RHS remaining.
Xirdus 4 hours ago [-]
I think most of the difficulty in explaining monads is the IO monad specifically. Leave that off and monads seem almost trivial. Add it back and whatever trivial mental model you just built stops working altogether.
caaqil 10 hours ago [-]
> things that makes monads notoriously difficult to explain to people outside of the Haskell ecosystem
Huh, I thought a monad is just a monoid in the category of endofunctors, what's the problem?
krackers 17 minutes ago [-]
It's about as difficult as describing vectors in math. Vectors aren't really "little arrows" or "things with magnitude and direction" (because then functions wouldn't count), I think there's a similarly cryptic saying that "a vector is just an element of a vector space" or "a tensor is anything that transforms like a tensor".
In both cases what they really are is an abstract concept defined by things that satisfy some definitions, but in practice it's easier to just look at a few concrete examples like Maybe.
girvo 10 hours ago [-]
And here I was convinced that they’re burritos!
I will say that explaining functors to my coworkers was always much simpler, and then naturally led to monads
hexasquid 8 hours ago [-]
I suspect all branches of tech have their rite of passage.
For haskellers, it is writing a monad tutorial
For javascripters, it is writing a framework
For make-stuff-with-LLM people, it is producing a diagram explaining your code generation factory
... and so on
16 minutes ago [-]
rramadass 9 hours ago [-]
Some OO languages have a parallel concept in "Metaclass" (https://en.wikipedia.org/wiki/Metaclass) i.e. the class of a class which is structurally similar to "Kind" though of course the mechanics are quite different.
I still have an old book Putting Metaclasses to Work - A New Dimension in Object-Oriented Programming by Forman et.al somewhere.
The key difference is that the Type guys do it at compile time (correctness) while the OO guys do it at runtime (flexibility).
epolanski 10 hours ago [-]
The thing that makes monads difficult is laziness and the anti math circle jerk.
You start from a functor which is a data type that admits a map operation.
Then you add of, or pure, to make an applicative functor, and then you add flatmap to make a monad.
windward 8 hours ago [-]
It's odd, the stereotype of software developers is a know-it-all attitude, but start talking about types and you get excuses that boil down to 'I'm incapable of learning to understand the thing you learned to understand'.
deterministic 6 hours ago [-]
I used to be a big Haskell fan and had a lot of fun writing and using monads. And I agree that once you get them, the basic idea is quite simple. I’ve even used monads to prove a small compiler correct in Lean.
But in practice, I think monads are a poor tool for large-scale software development. I spent a long time trying to build a large game in Haskell and eventually gave up out of frustration with the complexity they introduced.
Monads make sense as a way to manage effects in pure languages like Haskell/Lean/Idris/Agda. But outside that context, I don’t think the additional abstraction is worth the cost.
Understanding how monads work is one thing. Understanding why they’re needed in the first place, and when they’re not, is another.
djtango 5 hours ago [-]
OCaml isn't pure and neither is F# iirc do you see a bit less monad heavy code in large codebases there?
What sort of situations would you want to give up a little purity or error handling at the type level? Maybe at a boundary where you can just write a very ugly imperative but quarantined section to give it a simpler type?
LoganDark 9 hours ago [-]
> The thing that makes monads difficult is laziness and the anti math circle jerk.
That's quite a hateful way to describe lack of knowledge.
There are some prerequisites that make it easier to understand monads, and a lot of programmers don't even know those. That can make learning monads difficult right off the bat.
epolanski 9 hours ago [-]
No there's no complexity behind monads except laziness and anti math circle jerk.
It's really basic concepts, much easier than the analysis you do in high school.
But people would rather sit through monad as boxes or burritos cringe tutorials.
xboxnolifes 9 hours ago [-]
Your math explanation starts with a math concept that the vast, vast, vast majority of people will have never even heard of, even in math-adjacent fields. It's not anti-math reasoning. It's a lack of self-awareness that your background is very math heavy, which makes that particular explanation make sense to you. I've gone my entire schooling, as a student who was always one of the few who were "good at math", all the the way through my CS education, without ever having heard of a functor until I came across the Haskell monad explanation meme.
Your next step could be to blame the school systems if you want, but that doesn't help the situation at hand.
fn-mote 8 hours ago [-]
>> You start from a functor which is a data type that admits a map operation.
Then you add of, or pure, to make an applicative functor, and then you add flatmap to make a monad.
> Your math explanation starts with a math concept that the vast, vast, vast majority of people will have never even heard of, even in math-adjacent fields
Let me take this apart.
1. a data type that admits a map operation
Present in most languages including JavaScript. Not a mathematical issue.
2. add of, or pure, to make an applicative functor
I had to look this up. It was new content for me, maybe 5 or 10 minutes believing I would get it.
3. add flatmap
This didn’t scare me but maybe it scares others. Not a mathematical issue here, though. Being in C++ doesn’t make this concept mainstream, I guess?
I majored in mathematics and I learned category theory before touching any serious Haskell, and I still found most of the "monad tutorials" kind of opaque and handwavy, and I think there are inherently non-trivial details unrelated to the notion of functor etc.
LoganDark 6 hours ago [-]
FWIW I dropped out of high school to pursue programming more directly. I regret it now because of the job market crash, but it's also too late for me to go back.
9 hours ago [-]
epgui 8 hours ago [-]
Lack of knowledge is not the problem at all. Anti-intellectualism is.
LoganDark 8 hours ago [-]
You can try to blame lack of knowledge on anything you want, but at the end of the day Hacker News is for intellectual curiosity and even HN readers don't all know what monads are.
deterministic 6 hours ago [-]
[dead]
childintime 5 hours ago [-]
What is the article actually saying? It doesn't seem the author actually knows.
It seems a tale about abstraction land, and how it never allows you to exit it again. How you pay taxes for every day spent in it, sold to you as a solution to a problem you didn't think you had.
As any problem can be expressed in C, how would what the author did look like in C? KISS. Abstraction land clearly protects one from such vulgarities.
hutao 4 hours ago [-]
The gist is that "kinds" allow you to write generic types which themselves take generic types as type parameters.
For a concrete example, suppose that you want to define a data type for JSON objects. You would likely need to choose a type to represent JSON arrays (such as `ArrayList`, in Java), as well as a type to represent JSON objects (such as `HashMap`). However, what if the client wanted to use `TreeMap` instead of `HashMap`?
With "kinds," you would be able to define the JSON data type as something abstract like `JSON<ArrayType, MapType>`, and allow the client to instantiate it as `JSON<ArrayList, HashMap>` or `JSON<ArrayList, TreeMap>`. Unfortunately, in a language such as Java, this is not possible, because `ArrayList`, `HashMap`, and `TreeMap` are themselves generic types, while only non-generic types (such as `Integer`, `String`, or `HashMap<String, Integer>`) can be type parameters. However, in a language with higher-kinded types, such as Scala, this becomes possible.
In these higher-kinded languages, a type such as `Integer` or `HashMap<String, Integer>` would have a "kind" of `*`, while a generic type such as `ArrayList` would have a kind of `* -> *` (meaning: a generic type of one, non-generic type parameter), and `HashMap` and `TreeMap` would both have a "kind" of `* -> * -> *` (meaning: a generic type of two, non-generic type parameters).
C++'s templates permit a similar abstraction to higher-kinded types, and the library nlohmann::json (https://json.nlohmann.me/api/basic_json/) in fact does something very similar to the JSON example that I describe. Namely, it uses `std::vector` as the default for `ArrayType` and `std::map` as the default for `MapType`, but also allows the client to parameterize the underlying container of choice. However, C++ templates need to be fully expanded in order to be typechecked, leading to compile-time error messages that can be long and difficult to read. A "kind" system basically solves this issue by imposing a proper type system onto templates, allowing for more understandable error messages.
How do you express "the thing you get back has the same type as the thing you're looking for"?
dwattttt 3 hours ago [-]
You don't. Or less snarky, I imagine you'd have to use macros to generate wrapper functions, in order to generate a new function of a user provided type.
vatsachak 9 hours ago [-]
One thing I've noticed since I've become an engineer who gets to design the architecture:
No matter what I do, eventually algebraic structures like semi-groups and monads along with categorical structure like lenses and traversals keep popping up.
skybrian 6 hours ago [-]
It seems like the sort of thing that system designers could benefit from understanding, but the ordinary users of a system shouldn't need to know about it.
ivanjermakov 8 hours ago [-]
In my experience such categories pop up when dealing with immutable data, which is common in many domains.
cat-whisperer 9 hours ago [-]
I really like the concept of dependent types, all type systems that I use rust or haskell have a very limited set of dependent types, but I do like the things that it let's you do
cbsmith 7 hours ago [-]
Missed opportunity to say "Kinds of kindness". ;-)
My main problem with Haskell is that it never fully embraced dependent types. Instead, it kept adding one type system extension after another, making the language unnecessarily complicated.
I used to be a big Haskell fan, until I discovered languages like Lean, Agda, and Idris 2 and realized how much cleaner and more coherent the whole thing could be with dependent types built in from the start.
I highly recommend Haskell fans explore Lean/Agda/Idris. They make Haskell feel like a pile of spaghetti by comparison.
antonvs 10 hours ago [-]
> These are the constructs we manipulate in our programs, things like 1, 2, True, "hello", etc… In functional programming contexts, they're also called "terms."
This is not correct. Terms are syntactic expressions in a program. For example, 1+2 is a term, but it’s not a value. At runtime, it evaluates to 3. As such, some terms correspond directly to values - like 1 - but other terms represent computations that will be performed at runtime.
tl;dr: terms are a syntactic concept, values are a runtime concept. Terms evaluate to values.
thaumasiotes 8 hours ago [-]
> This is not correct.
Why do you think so? It's a completely standard usage. For example, we can ask MathWorld:
> In logic, a term is a variable ['a', 'f'], constant ['1', '"hello"'], or the result of acting on variables and constants by function symbols ['1+2', 'f(a)'].
In particular, note that by this wording, if you're looking at '1+2', the "term" is not the string '"1+2"' but the result of adding '1' to '2', which is '3'.
You can talk about 'terms' in the context of how a compiler views source code. Or you can talk about 'terms' in the context of how type theory applies to some computation. They'll be called 'terms' either way; you seem to have staked out the position that the compiler context is obligatory and the type theory context is somehow in error.
rramadass 4 hours ago [-]
Terms are a syntactic concept while Values are a semantic runtime concept. The key to understanding is that Values are Terms which have been reduced (i.e. evaluated) to normal form and hence cannot be reduced further. The nuance lies in the fact that while both unreduced Terms and reduced Values can have semantic meanings, only at the Value stage is it canonical and fully computed.
In fact the "Calculus of Constructions" (CoC) derived from Typed Lambda Calculus unifies everything into a Term like so;
Term ::= variable | lambda abstraction | lambda application | dependant product | sorts (type of types)
But "term" is not a term that is restricted to computation. In normal mathematical usage, all values are always 'fully computed'. You may not personally know what they are, but that doesn't matter. These are just different names that we give to a single object.
antonvs 3 hours ago [-]
Your own quote tells you why that’s wrong.
Do you seriously want to argue this point?
> you seem to have staked out the position that the compiler context is obligatory and the type theory context is somehow in error.
I used the compiler to illustrate the issue to people who clearly aren’t familiar with type theory. You imply that you are, but in that case, how do you justify the nonsense quoted above?
antonvs 9 hours ago [-]
Following on from the above, this statement is also incomplete at best:
> Types are groups of related values.
This is an operational perspective which misses one of the most important aspects of types: in a statically typed language, types are a way to classify terms, i.e. they're not just about values.
As an example of why this distinction matters, consider this expression: 1 + "hello". In most statically typed languages, this will be rejected at compile time. It doesn't have to be evaluated (i.e. at runtime) to be rejected - it's invalid because the types of the terms - 1 and "hello" - aren't compatible.
This compile-time rejection is only possibly because types apply to terms, and terms aren't values. This is critical to the usefulness of statically-typed languages - without this, they'd have to run the program to detect type errors.
Benjamin Pierce's book "Types and Programming Languages" has some great coverage of this topic.
LoganDark 9 hours ago [-]
Do you mean that if types had only to do with values, you would have to evaluate the term to determine type?
Also, 1 + "hello" is "1hello", obviously. Have you never read JavaScript? :)
hexasquid 7 hours ago [-]
I gather it goes through an intermediate coercion step where it is "1" + "hello".
What trips us up is having existing expectations for what an operator called + should do.
Rendered at 08:44:38 GMT+0000 (UTC) with Wasmer Edge.
EDIT: also the links to GitHub on https://lsalpha.com/systems are 404ing for me, so I exited your site without an example of what a real implementation might look like.
https://en.wikipedia.org/wiki/Monad_transformer
A monad is any construct of abstraction that obeys the Monad Laws. This may seem a useless definition, but my point is that deriving a useful intuition for the concept of the monad is necessarily challenging because they cannot readily be boiled down any further than that without loss of information.
The best intuitive explanation I've developed so far is that a monad is an abstraction over structures of ordered computations. The most ready examples for non-functional programmers are probably exceptions (and their handlers) and nullable types (typically called "optional types" in languages with first-class handling of such things, unlike Java).
You don't need List<A> to write an IntList, nor do you need + to work on different integers in order to have a particular integer.
Otherwise memorize a little bit and dispose of the mystery. It’s not that far out.
ArrayList's kind is type->type
ArrayList<A>'s kind is type.
The A is what plugs into type on the LHS of the arrow, leaving only the RHS remaining.
Huh, I thought a monad is just a monoid in the category of endofunctors, what's the problem?
In both cases what they really are is an abstract concept defined by things that satisfy some definitions, but in practice it's easier to just look at a few concrete examples like Maybe.
I will say that explaining functors to my coworkers was always much simpler, and then naturally led to monads
For haskellers, it is writing a monad tutorial
For javascripters, it is writing a framework
For make-stuff-with-LLM people, it is producing a diagram explaining your code generation factory
... and so on
I still have an old book Putting Metaclasses to Work - A New Dimension in Object-Oriented Programming by Forman et.al somewhere.
There is also the classic The Art of the Metaobject Protocol by Kiczales et.al which is quie relevant here - https://en.wikipedia.org/wiki/The_Art_of_the_Metaobject_Prot...
The key difference is that the Type guys do it at compile time (correctness) while the OO guys do it at runtime (flexibility).
You start from a functor which is a data type that admits a map operation.
Then you add of, or pure, to make an applicative functor, and then you add flatmap to make a monad.
But in practice, I think monads are a poor tool for large-scale software development. I spent a long time trying to build a large game in Haskell and eventually gave up out of frustration with the complexity they introduced.
Monads make sense as a way to manage effects in pure languages like Haskell/Lean/Idris/Agda. But outside that context, I don’t think the additional abstraction is worth the cost.
Understanding how monads work is one thing. Understanding why they’re needed in the first place, and when they’re not, is another.
What sort of situations would you want to give up a little purity or error handling at the type level? Maybe at a boundary where you can just write a very ugly imperative but quarantined section to give it a simpler type?
That's quite a hateful way to describe lack of knowledge.
There are some prerequisites that make it easier to understand monads, and a lot of programmers don't even know those. That can make learning monads difficult right off the bat.
It's really basic concepts, much easier than the analysis you do in high school.
But people would rather sit through monad as boxes or burritos cringe tutorials.
Your next step could be to blame the school systems if you want, but that doesn't help the situation at hand.
> Your math explanation starts with a math concept that the vast, vast, vast majority of people will have never even heard of, even in math-adjacent fields
Let me take this apart.
1. a data type that admits a map operation
Present in most languages including JavaScript. Not a mathematical issue.
2. add of, or pure, to make an applicative functor
I had to look this up. It was new content for me, maybe 5 or 10 minutes believing I would get it.
3. add flatmap
This didn’t scare me but maybe it scares others. Not a mathematical issue here, though. Being in C++ doesn’t make this concept mainstream, I guess?
https://en.cppreference.com/cpp/container/flat_map
It seems a tale about abstraction land, and how it never allows you to exit it again. How you pay taxes for every day spent in it, sold to you as a solution to a problem you didn't think you had.
As any problem can be expressed in C, how would what the author did look like in C? KISS. Abstraction land clearly protects one from such vulgarities.
For a concrete example, suppose that you want to define a data type for JSON objects. You would likely need to choose a type to represent JSON arrays (such as `ArrayList`, in Java), as well as a type to represent JSON objects (such as `HashMap`). However, what if the client wanted to use `TreeMap` instead of `HashMap`?
With "kinds," you would be able to define the JSON data type as something abstract like `JSON<ArrayType, MapType>`, and allow the client to instantiate it as `JSON<ArrayList, HashMap>` or `JSON<ArrayList, TreeMap>`. Unfortunately, in a language such as Java, this is not possible, because `ArrayList`, `HashMap`, and `TreeMap` are themselves generic types, while only non-generic types (such as `Integer`, `String`, or `HashMap<String, Integer>`) can be type parameters. However, in a language with higher-kinded types, such as Scala, this becomes possible.
In these higher-kinded languages, a type such as `Integer` or `HashMap<String, Integer>` would have a "kind" of `*`, while a generic type such as `ArrayList` would have a kind of `* -> *` (meaning: a generic type of one, non-generic type parameter), and `HashMap` and `TreeMap` would both have a "kind" of `* -> * -> *` (meaning: a generic type of two, non-generic type parameters).
C++'s templates permit a similar abstraction to higher-kinded types, and the library nlohmann::json (https://json.nlohmann.me/api/basic_json/) in fact does something very similar to the JSON example that I describe. Namely, it uses `std::vector` as the default for `ArrayType` and `std::map` as the default for `MapType`, but also allows the client to parameterize the underlying container of choice. However, C++ templates need to be fully expanded in order to be typechecked, leading to compile-time error messages that can be long and difficult to read. A "kind" system basically solves this issue by imposing a proper type system onto templates, allowing for more understandable error messages.
See also: Concepts for higher-kinded types in C++20 - https://mmore500.com/cse-491/blog/2020/02/24/concepts-monads...
This is C.
How do you express "the thing you get back has the same type as the thing you're looking for"?No matter what I do, eventually algebraic structures like semi-groups and monads along with categorical structure like lenses and traversals keep popping up.
I used to be a big Haskell fan, until I discovered languages like Lean, Agda, and Idris 2 and realized how much cleaner and more coherent the whole thing could be with dependent types built in from the start.
I highly recommend Haskell fans explore Lean/Agda/Idris. They make Haskell feel like a pile of spaghetti by comparison.
This is not correct. Terms are syntactic expressions in a program. For example, 1+2 is a term, but it’s not a value. At runtime, it evaluates to 3. As such, some terms correspond directly to values - like 1 - but other terms represent computations that will be performed at runtime.
tl;dr: terms are a syntactic concept, values are a runtime concept. Terms evaluate to values.
Why do you think so? It's a completely standard usage. For example, we can ask MathWorld:
> In logic, a term is a variable ['a', 'f'], constant ['1', '"hello"'], or the result of acting on variables and constants by function symbols ['1+2', 'f(a)'].
(I've added the bracketed material, which provides examples of the senses that MathWorld gives. https://mathworld.wolfram.com/Term.html )
In particular, note that by this wording, if you're looking at '1+2', the "term" is not the string '"1+2"' but the result of adding '1' to '2', which is '3'.
You can talk about 'terms' in the context of how a compiler views source code. Or you can talk about 'terms' in the context of how type theory applies to some computation. They'll be called 'terms' either way; you seem to have staked out the position that the compiler context is obligatory and the type theory context is somehow in error.
In fact the "Calculus of Constructions" (CoC) derived from Typed Lambda Calculus unifies everything into a Term like so;
Term ::= variable | lambda abstraction | lambda application | dependant product | sorts (type of types)
For reference see Brandl's work that i list here - https://news.ycombinator.com/item?id=50028902
Do you seriously want to argue this point?
> you seem to have staked out the position that the compiler context is obligatory and the type theory context is somehow in error.
I used the compiler to illustrate the issue to people who clearly aren’t familiar with type theory. You imply that you are, but in that case, how do you justify the nonsense quoted above?
> Types are groups of related values.
This is an operational perspective which misses one of the most important aspects of types: in a statically typed language, types are a way to classify terms, i.e. they're not just about values.
As an example of why this distinction matters, consider this expression: 1 + "hello". In most statically typed languages, this will be rejected at compile time. It doesn't have to be evaluated (i.e. at runtime) to be rejected - it's invalid because the types of the terms - 1 and "hello" - aren't compatible.
This compile-time rejection is only possibly because types apply to terms, and terms aren't values. This is critical to the usefulness of statically-typed languages - without this, they'd have to run the program to detect type errors.
Benjamin Pierce's book "Types and Programming Languages" has some great coverage of this topic.
Also, 1 + "hello" is "1hello", obviously. Have you never read JavaScript? :)
What trips us up is having existing expectations for what an operator called + should do.