Hacker News new | past | comments | ask | show | jobs | submit login
Haskell Mini-Patterns Handbook (kowainik.github.io)
328 points by gluegadget on Aug 18, 2020 | hide | past | favorite | 30 comments



This is great. I can say I've used and encountered literally all of these techniques many times in every Haskell codebase I've joined and built.

The concise cost/benefit analysis very helpful.


This is a gem. I have been looking for this kind of clear and to the point explanations of common Haskell patterns for a long time.


These patterns are not only applicable to Haskell but FP and programming in general. I am using them with a Typescript codebase.


This is a great post, I’ve seen a lot of those patterns before and it’s nice to have them in one place! One criticism I have though is I’m not sure about the advantage of phantom types, it seems like boilerplate to me. I feel like generalized algebraic data types are a strictly better pattern to follow to solve the issue of restricting type variable bindings to specific types.


I introduced phantom types to a codebase recently, although it was Scala rather than Haskell. The code was using arrays of bytes, where:

- Some contain plaintext, some contain ciphertext

- Some contain keys, some contain data

- The keys themselves exist in plaintext and ciphertext forms (data keys generated by AWS KMS)

- Some keys were used for signing/verifying, some were used for encrypting/decrypting

- Some arrays contain Base64 data (for embedding in JSON), some contain unencoded data

Using 'Array[Byte]' would work, but would be error-prone. Using distinct types or wrappers like 'Base64[Plaintext[Key[Signing]]]' would require lots of extra definitions, and wrapping/unwrapping scattered around the code. Phantom types let me use type signatures with the right amount of specificity and polymorphism as needed, whilst the code itself was the 'straightforward' version without wrapping/unwrapping.


> I feel like generalized algebraic data types are a strictly better pattern to follow to solve the issue of restricting type variable bindings to specific types.

GADTs which don't store a value based on one of their type parameters are still making use of phantom types. The key concept with a GADT is that you can have type variable binding(s) (whether phantom or concrete) which are determined by the constructor. If you aren't taking advantage of that then you're probably just defining an ordinary data type using GADT syntax.


Phantom types can be useful when modelling currency: the "implementation" stays the same but you want to avoid summing two different currencies by mistake.

https://ren.zone/articles/safe-money


Thanks for this. The tasks are a nice touch!


Anyone else have trouble with the page on firefox? I get a giant "kowainik" written on the forefront of the page, and I don't know how to close it.


I am using Firefox and it works without problems. 79.0 on macOS Mojave.


Working fine on Firefox 68.11.0-ESR on Linux for me.


Deleting it in the inspector works.


looks fine on 79.0 (Windows, w/ ublock)


I tried with Chrome and it worked.


I do not write Haskell, but this is a neat site.


This is a beautiful start but much more content. Send PRs to kowainik.


The most deadly sin with Haskell is introducing unnecessary redundant abstractions to impress other people.

Imagine aircraft engineers will do that.

Up to (but not including) NonEmpty lists everything was fine.


> Up to (but not including) NonEmpty lists everything was fine.

"but not including" how in the world is NonEmpty redundant abstraction?


Do you have some examples?


The whole "this code is problematic because... " section on Boolean blindness reminds me of the worst caricatures of left-wing narratives of oppression based on PhD-level Critical Theory.

Look, a lot of the other patterns are interesting but I really think that if you are getting into criticising code because it has "Boolean blindness" you are probably getting a bit lost in navel gazing. Do you really have nothing better to do?


Their particular example of boolean blindness was perhaps too simple, but it's definitely a useful thing to avoid. For example, this sort of thing comes up a lot:

    foo x y z = if (isNothing x) then bar y else baz z
      where bar val = [val]
            baz val = [fromJust x, val]
Here we're using a boolean like 'isNothing x' to choose what to do, but one of those choices (baz) is making implicit assumptions about the meaning of that choice ('fromJust x' is an unsafe function, which will crash if x isn't 'Just'). In real code this sort of implicit coupling can spread across larger pieces of code, with more complicated and less obvious behaviour (e.g. indexing into a list, assuming it's safe due to some arithmetic written elsewhere). This is bad for a few reasons:

- If we don't get it exactly right (including edge cases, etc.) then it can go pretty badly wrong (a crash in this case).

- The dependency/coupling between the check and the assumption are implicit and may break in the future; e.g. if we try to re-use the assumption-riddled code without performing the check; or if the condition in the check needs to change and we don't realise it breaks the assumption-riddled code.

- The logic usually ends up being overly-complicated, since we're performing redundant work. In my above example we could do this instead:

    foo x y z = maybe (bar y) (baz z) x
      where bar val    = [val]
            baz val x' = [x', val]
Branching on the value we care about ('x', using the 'maybe' function) ensures that each branch (a) has the context it needs to do it's job (e.g. the x' parameter) and (b) isn't given any more context than what is known at that point (e.g. the compiler can tell us if we're forgetting unhandled cases, which it can't if our assumptions are implicit).


I work at a Haskell shop, and the overuse 'boolean blindness' issue has come up. The common understanding is that booleans are fine to use when the problem calls for it, just don't don't be afraid to use a more expressive data structure if that could make the code concise or understandable.

I think the real "boolean blindless" is something that happens in beginners, who don't yet understand that ADTs can be a rich modeling language, and instead use (Bool, Bool, Bool, Bool) when some combination of product and sum types is better.



I recommend reading this article that's linked in the evidence section: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...


Hearty lol here. How do you associate some advice to use the type system to explain the meaning of your booleans to left-wing narratives of oppression?

Or are you conflating booleans with black/white and arguing for colour blindness because All Bools Matter or something?

I can't really make sense of this.


do you have any criticisms with actual substance? why do you consider those techniques "navel gazing"?


I really feel that too many rules like this lead beginners astray and focus on issues that make very little difference in delivering a product.

If beginners see a list like this, and start re-factoring the code to remove "Boolean blindness", that's likely not the best way for them to spend their time and the code may become more verbose in the process.

I prefer not to dwell on negativity about code, as long as it does the job and is implemented in a reasonably simple manner. I think in the Haskell community sometimes there can be an unhealthy tendency to nitpick the implementation details.


I wouldn't consider them rules inasmuch as they're things to keep in mind when working in a Haskell codebase, in much the same way GoF has been useful for OO programmers for decades.

Can you write code without using a pattern from GoF? Sure. Will it work? Probably. Does that make GoF useless? No, I'd think not. This type of content is immensely valuable for beginner and practitioner alike because it starts to build a shared language with which to talk about how codebases are organized :)


As a beginner you're just trying to keep your head above water and get the damn thing to compile. For me at least I cared more about that than making the code pretty.

Moreover I don't see much harm refactoring your code with these patterns and maybe losing time, because most people learn haskell off the job.


That's an awfully long-winded and confusing way to say you think "Boolean blindness" is too nitpicky. Personally, it seems like a pretty valid idea to keep in mind, especially as developers transition from a conventional imperative language to Haskell. Making frequent boolean checks is pretty normal in that space, and pattern matching is less common, which might lead new Haskell programmers to write unidiomatic code.




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

Search: