@@ -18,9 +18,9 @@ With a new age of software development coming, what does it even mean to write
1818good, robust, correct code? It is long overdue to clarify exactly the mindset
1919on which we approach and define "good" coding principles.
2020
21- In this series, [ * Five-Point Haskell* ] [ Five-Point Haskell ] , let's set out to
22- establish a five-point unified framework of the "Typed Functional Programming"
23- (and Haskell-derived) programming philosophy aimed to create code that is
21+ In this series, * [ Five-Point Haskell] [ ] * , let's set out to establish a
22+ five-point unified framework of the "Typed Functional Programming" (and
23+ Haskell-derived) programming philosophy aimed to create code that is
2424maintainable, correct, long-lasting, extensible, and beautiful to write and
2525work with. We'll try to reference real world case studies with and actual
2626examples when we can, and also attempt to dispel thought leader sound-bytes
@@ -338,10 +338,25 @@ and has been the source of many frustrating bug hunts.
338338[ sock_sendpage ] : https://www.rapid7.com/db/modules/exploit/linux/local/sock_sendpage/
339339[ gcp ] : https://www.thousandeyes.com/blog/google-cloud-outage-analysis-june-12-2025
340340
341- Why do we do this to ourselves? Because it is convenient. It's not easy to make
342- a "integer or not found" type in C or javascript without some sort of
343- side-channel. Imagine if javascript's ` String.indexOf() ` instead expected
344- continuations on success and failure and became much less usable as a result:
341+ There's also [ CVE-2008 -5077] [ ] , because [ EVP_VerifyInit] [ ] returns ` 0 ` for
342+ false, ` 1 ` for true, and ` -1 ` for error! So some OpenSSL code did a simple
343+ if-then-else check (` result != 0 ` ) and treated error and true the same way.
344+ Whoops.
345+
346+ [ CVE-2008-5077 ] : https://www.invicti.com/web-application-vulnerabilities/openssl-improper-input-validation-vulnerability-cve-2008-5077
347+ [ EVP_VerifyInit ] : https://docs.openssl.org/1.1.1/man3/EVP_VerifyInit/
348+
349+ Why do we do this to ourselves? Because it is convenient. In the case of
350+ ` EVP_VerifyInit ` , we can define an enum instead...
351+
352+ ``` haskell
353+ data VerifyResult = Success | Failure | Error
354+ ```
355+
356+ However , it's not easy to make a " integer or not found" type in C or javascript
357+ without some sort of side- channel. Imagine if javascript's `String. indexOf() `
358+ instead expected continuations on success and failure and became much less
359+ usable as a result:
345360
346361```haskell
347362unsafeIndexOf :: String -> String -> Int
@@ -546,6 +561,54 @@ getUser conn uid = do
546561Pushing it to the driver level will also unify everything with the driver's
547562error-handling system.
548563
564+ ### Boolean Blindness
565+
566+ At the heart of it, the previous examples' cardinal sin was "boolean
567+ blindness". If we have a predicate like ` validUsername :: String -> Bool ` , we
568+ will branch on that ` Bool ` once and throw it away. Instead, by having a
569+ function like ` mkUsername :: String -> Maybe Username ` , we _ keep_ the proof
570+ alongside the value for the entire lifetime of the value. We basically pair
571+ the string with its proof forever, making them inseparable.
572+
573+ There was another example of such a thing earlier: instead of using `null ::
574+ [ a] -> Bool` and gating a call to ` mean` with ` null`, we instead use
575+ ` nonEmpty :: [a] -> Maybe (NonEmpty a) ` , and pass along the proof of
576+ non-emptiness alongside the value itself. And, for the rest of that list's
577+ life, it will always be paired with its non-emptiness proof.
578+
579+ Embracing total depravity means always keeping these proofs together, with the
580+ witnesses bundled with the value itself, because if you don't, someone is going
581+ to assume it exists when it doesn't, or drop it unnecessarily.
582+
583+ Boolean blindness also has another facet, which is where ` Bool ` itself is not a
584+ semantically meaningful type. This is "semantic boolean blindness".
585+
586+ The classic example is ` filter :: (a -> Bool) -> [a] -> [a] ` . It might sound
587+ silly until it happens to you, but it is pretty easy to mix up if ` True ` means
588+ "keep" or "discard". After all, a "water filter" only lets water through, but a
589+ "profanity filter" only rejects profanity. Instead, how about `mapMaybe :: (a
590+ -> Maybe b) -> [ a] -> [ b] ` ? In that case, it is clear that ` Just` results are
591+ kept, and the ` Nothing ` results are discarded.
592+
593+ Sometimes, the boolean is ambiguous to what it means. You can sort of interpret
594+ the [ 1999 Mars Polar Lander] [ polar ] crash this way. Its functions took a
595+ boolean based on the state of the legs:
596+
597+ [ polar ] : https://en.wikipedia.org/wiki/Mars_Polar_Lander
598+
599+ ``` haskell
600+ deployThrusters :: Bool -> IO ()
601+ ```
602+
603+ and `True ` and `False ` were misinterpreted. Instead , they could have considered
604+ semantically meaningful types: (simplified)
605+
606+ ```haskell
607+ data LegState = Extended | Retracted
608+
609+ deployThrustrs :: LegState -> IO ()
610+ ```
611+
549612### Resource Cleanup
550613
551614Clean - up of finite system resources is another area that is very easy to assume
0 commit comments