Hacker News new | past | comments | ask | show | jobs | submit login
Haskell mini-patterns handbook (2020) (kowainik.github.io)
164 points by Tomte on Sept 4, 2023 | hide | past | favorite | 19 comments



I'm slowly grokking Haskell and I like the genius (or madness?) of having the type system do a lot to verify that the program is correct.

I just started reading about liquid types and I'm loving the idea : https://news.ycombinator.com/item?id=37349276


Most of these are also useful in other languages (that have a good enough type system). I use them all the time in C++ and TypeScript.


Because C++ lacks enforcement ("strong types, weakly checked") and loves implied conversions, when you mimick these patterns you don't get all of their benefits, which can result in misplaced confidence.

Take the most simple example, suppose I have an enumerated type, all these HedgehogSizes are either TINY or NORMAL or HUGE. Awesome. But, in C++ a HedgehogSize can be 6. Um. What? Oh says C++ I could see that's just an integer, and 6 is an integer so that's fine... Right?


I broadly agree with the lots-of-pitfalls nature, but regarding the enum case specifically, which version of C++ are you talking about, and in what syntactic context? The later-introduced “enum class” makes things somewhat tighter than C-style enums.


All versions of C++, all existing versions and likely any potential future versions.

In C++ both these enum types have an "underlying type" which is just an integer. That's the type you're really getting for most purposes.

In terms of syntax specifically you can cast (in C++ specifically static cast) integers to an enumerated type. This makes mechanical sense to C++ because of the compatible "underlying type" but as a direct consequence you don't have the kind of type safety these exercises are relying on.

Notice that if I cast 1.234 to an integer, C++ says that's 1. It does know that cast doesn't mean "just change the type but keep the same bits" and yet, that's exactly what happens for enum.


I would love it if C++ had real ADTs.


std::variant comes pretty close, no?


std::variant is a poor imitation. The C++ type system can't really do this, so it's contorted to try to get as close as we can, and the results aren't pretty.

1. Consider this Rust type: enum InvisibleDog { } - this is an Empty Type, there are no Invisible Dogs, Rust is OK with the existence of this type, although since it's uninhabited there can be no instances (and the type accordingly has no size, not zero size, no size at all). Such types don't make a whole lot of sense concretely, but they're crucial to good generic programming.

In C++ we can't have such a type, the closest we can attempt is a std::variant whose only actual variant is valueless_by_exception and has a size of at least 1 byte. This is a disaster because it completely fails to achieve what we meant.

2. OK how about enum JustOneByte { Byte(u8) } - this type is as its name suggests, just a byte, Rust's u8 type. Its representation is accordingly one byte.

But with std::variant we can't do that either, C++ has to account for valueless_by_exception and so it has to carry around a discriminator, so that it can discriminate between Byte, the only actual possible value of this type, and valueless_by_exception. As a result in C this type is larger than its "real" content, which is just one byte.

3. We can't do the guaranteed niche optimisation either. Rust's Option is literally just a sum type. It's not magic (in that sense, it's effectively a langitem so it is magic in some other ways). But despite the lack of magic Option<&T> is guaranteed to be the same size as &T is thanks to the guaranteed niche optimisation. C++ can't do that either.


I had never heard of inverseMap. I suppose it only works where every variant can only be parsed in the same way it is rendered.


This is a formal property of a function, called an injective function: https://en.wikipedia.org/wiki/Injective_function


Anything like this for Forth?


Do we have something like this but for rust?


All of these except MonadFail and bidirectional parsing can be applied to Rust.


Sorry if I'm missing the obvious, but why wouldn't bidirectional parsing work in Rust?


It would, but it's just not as common as deriving those methods with macros, like from the enum_derive crate or similar.




That book I added some months ago to https://rust-lang.guide. Love it, learned from it quite some bits :)





Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: