Live data from Hacker News

Haskell Mini-Patterns Handbook

kowainik.github.io

31–34 of 34 posts

Re: Haskell Mini-Patterns Handbook

#31
post #25

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.

Re: Haskell Mini-Patterns Handbook

#32

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 be…

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).

Re: Haskell Mini-Patterns Handbook

#33
post #25

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 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.

Re: Haskell Mini-Patterns Handbook

#34

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?

Post reply on HN