@@ -8,19 +8,24 @@ slug: five-point-haskell-part-1-total-depravity
88series : five-point-haskell
99---
1010
11- Times have changed. The entire discipline of software development and
12- engineering is under question, and over the past years has come under multiple
13- movements re-thinking what it even means to write good, robust, correct code.
14- Multiple language and framework wars have been fought, the zeitgeist constantly
15- shifts. It is long overdue to clarify exactly the mindset on which we approach
16- and define "good" coding principles.
17-
18- In [ this series] [ Five-Point Haskell ] , let's set out to establish a five-point
19- unified framework of the "Typed Functional Programming" (and Haskell-derived)
20- programming philosophy aimed to create code that is maintainable, correct,
21- long-lasting, extensible, and beautiful to write and work with. These points
22- will attempt to dispel thought leader sound-bytes that have become all too
23- popular on Twitter --- "heresies", if you may permit the terminology.
11+ I have thought about distilling the principles in which I program Haskell, and
12+ how I've been able to steer long-lived projects (both personal and on teams)
13+ over years of growth, refactorings, and changes in demands. I find myself
14+ coming back to a few distinct and helpful "points" --- "doctrines", if you may
15+ allow me to say --- that have yet to lead me astray.
16+
17+ With a new age of software development coming, what does it even mean to write
18+ good, robust, correct code? It is long overdue to clarify exactly the mindset
19+ on which we approach and define "good" coding principles.
20+
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
24+ maintainable, correct, long-lasting, extensible, and beautiful to write and
25+ work with. We'll try to reference real world case studies with and actual
26+ examples when we can, and also attempt to dispel thought leader sound-bytes
27+ that have become all too popular on Twitter --- "heresies", if you may permit
28+ the terminology.
2429
2530[ Five-Point Haskell ] : https://blog.jle.im/entries/series/+five-point-haskell.html
2631
@@ -59,7 +64,7 @@ be better and better at keeping more in your mind.
5964Actually _ addressing_ these issues in most languages requires a lot of overhead
6065and clunkiness. But luckily we're in Haskell!
6166
62- ### ID Mix-ups
67+ ### Explicit Tags
6368
6469The [ 2022 Atlassian Outage] [ atlassian ] , in some part, was the result of passing
6570the wrong type of ID. The operators were intended to pass _ App_ IDs, but
@@ -231,6 +236,12 @@ it (because it can run any `DbConnection a`)...but if any sub-function of a
231236sub-function calls ` clearTestEnv ` , it will have to unite with `DbConnection
232237Test`, which is impossible for a prod connection.
233238
239+ This is somewhat similar to using "mocking-only" subclasses for dependency
240+ injection, but with a closed universe. I discuss patterns like this in my
241+ [ Introduction to Singletons] [ singletons ] series.
242+
243+ [ singletons ] : https://blog.jle.im/entries/series/+introduction-to-singletons.html
244+
234245Correct Representations
235246-----------------------
236247
@@ -304,8 +315,6 @@ There are examples:
304315* C's ` fgetc() ` , ` getchar() ` , returns -1 for ` EOF ` . And if you cast to
305316 ` char ` , you basically can't distinguish EOF from ` 0xff ` , ` ÿ ` .
306317* ` malloc() ` returning the pointer 0 means not enough memory
307- * In other cases, an integer pointer being ` 0 ` means null pointer, a default
308- null value for non-existence
309318* Some languages have a special ` NULL ` pointer value as well --- or even a
310319 value ` null ` that can be passed in for any expected type or object or
311320 value.
@@ -343,6 +352,10 @@ unsafeIndexOf :: String -> String -> Int
343352indexOf :: String -> String -> (Int -> r ) -> (() -> r ) -> r
344353```
345354
355+ All of this just to [fake having actual sum types][faking].
356+
357+ [faking]: https:// blog. jle. im/ entry/ faking- adts- and - gadts. html
358+
346359We don't really have an excuse in Haskell , since we can just return `Maybe `:
347360
348361```haskell
@@ -410,8 +423,10 @@ already return `NonEmpty` by default (like `some :: f a -> f (NonEmpty a)` or
410423` group :: Eq a => [a] -> [NonEmpty a] ` ), allowing you to beautifully chain
411424post-conditions directly into pre-conditions.
412425
413- Sometimes the answer and issue will be a bit more subtle. This is our reminder
414- to never let these implicit assumptions go unnoticed.
426+ Accessing containers is, in general, very fraught...even things like indexing
427+ lists can send us into a graveyard spiral. Sometimes the answer and issue will
428+ be a bit more subtle. This is our reminder to never let these implicit
429+ assumptions go unnoticed.
415430
416431### Separate Processed Data
417432
@@ -434,9 +449,9 @@ saveUser :: Connection -> String -> IO (Maybe UUID)
434449saveUser conn s
435450 | validUsername s = do
436451 newId <- query conn " INSERT INTO users (username) VALUES (?) returning user_id" (Only s)
437- case newId of
438- [] -> pure Nothing
439- Only i : _ -> pure ( Just i)
452+ pure $ case newId of
453+ [] -> Nothing
454+ Only i : _ -> Just i
440455 | otherwise = pure Nothing
441456
442457getUser :: Connection -> UUID -> IO (Maybe String )
@@ -637,11 +652,13 @@ processAll :: Map Username Handle -> IO (Set Username)
637652allocateFile :: FilePath -> ResourceT IO (ReleaseKey , Handle )
638653allocateFile fp = allocate (openFile fp ReadMode ) hClose
639654
655+ -- Guarantees that all handlers will eventually close, even if `go` crashes
640656doTheThings :: Map Username FilePath -> IO ()
641657doTheThings paths = runResourceT $ do
642658 releasersAndHandlers <- traverse allocateFile paths
643659 go releasersAndHandlers
644660 where
661+ -- normal operation: slowly releases handlers as we drop them
645662 go :: Map Username (ReleaseKey , Handle ) -> ResourceT IO ()
646663 go currOpen = do
647664 toClose <- liftIO $ processAll (snd <$> currOpen)
@@ -709,6 +726,10 @@ anything, it might be _the_ bottleneck. If we can provide LLMs with properly
709726giving them the right iterative tooling --- I (maybe naively) believe this
710727might be the key to unlocking the full potential of agentic coding.
711728
729+ And...not whatever [ this tweet is] [ tweet ] .
730+
731+ [ tweet ] : https://x.com/rywalker/status/2003525268821188746
732+
712733### The Next Step
713734
714735Total depravity is all about using types to _ prevent errors_ . However, most
0 commit comments