WIP: A Haskell Pre­lude from the Groundish Up Part One: Lists and Num­bers

Start­ed
Pub­lished
Last up­dat­ed (com­men­tary)

The blog post you are about to see is a prod­uct of its time#

This is old and dumb. I had fun do­ing it, so I don’t re­gret it, but it was pret­ty sil­ly of me to do this when I real­ly didn’t know much Haskell (I know less now, though :P). I will keep my­self from adding com­men­tary (oth­er than this) be­cause that would get old quick. Also, the orig­i­nal thing al­ready has a bunch of com­men­tary FSR.

Why#

This is a pure­ly ed­u­ca­tion­al ex­er­cise, I have no real qualms with the stan­dard Haskell pre­lude. And, even if I did, this is clear­ly not a rea­son­able re­place­ment. A re­cur­sive data type for some­thing as foun­da­tion­al as in­te­gers, which is ex­act­ly what we’re go­ing to do in this ar­ti­cle, is asi­nine for some­thing that is used so of­ten by so many pro­grams. The op­ti­miza­tion of the stan­dard pre­lude, coun­ter­in­tu­itive­ly, is ex­act­ly my mo­ti­va­tion for mak­ing this in the first place. I want a ref­er­ence for a wide va­ri­ety of foun­da­tion­al func­tion­al pro­gram­ming con­cepts; com­pil­er-spe­cif­ic, some­what messy op­ti­miza­tions while, again, ben­e­fi­cial for Haskell as a lan­guage, are sim­ply an­ti­thet­i­cal to that pur­pose.

How#

I’m not go­ing to pre­tend I’m a pro­gram­ming ex­pert, or a func­tion­al pro­gram­ming ex­pert, or a Haskell ex­pert, or a math ex­pert, or that I’m at least pro­fi­cient at Haskell, or that

I’ve banged my head against the wall on this spe­cif­ic prob­lem enough that I feel con­fi­dent enough to go even fur­ther.

If you’re won­der­ing how you can fol­low along, all of the code need­ed will be shown in each post, or you can fol­low [this link](IN­SERT LINK TO git.py­rope.net/mbk/hgpu-1 HERE) to see all the code for this chap­ter in one place.

Main#

Our main isn’t go­ing to ac­tu­al­ly do any­thing per se, but it will serve as a use­ful ex­am­ple for how we’re do­ing things. I like hav­ing a sin­gle GHCi ses­sion with a file :l-oad­ed that I :r-eload when I make a change. By hav­ing a main file, we only need to load one file to mess with all of our stuff!

Let’s dive in head­first:

import qualified Prelude

Now load that and try typ­ing some num­bers:2

ƛ 1 + 2

<interactive>:10:3: error:
     Variable not in scope: (+) :: t0 -> t1 -> t
     Perhaps you meant Prelude.+ (imported from Prelude)

This shows us both the depth of the pre­lude and how we can cheat if3 we need to. If we real­ly, real­ly need to ac­cess a pre­lude func­tion we can use the qual­i­fied form. We shouldn’t need to, but I may use it for demon­stra­tion pur­pos­es.

Lists#

You might think that lists would be more com­pli­cat­ed than num­bers, but the meth­ods I’m plan­ning to use ac­tu­al­ly make lists eas­i­er than num­bers! Now tech­ni­cal­ly we could just use the nor­mal Haskell Ints, they aren’t even part of the pre­lude, but that’s lame and un­in­ter­est­ing. That real­ly is my ac­tu­al rea­son, but a bet­ter rea­son is that Ints are just too close­ly bound to the specifics of the com­pil­er.4

Let’s cre­ate List.hs and bear wit­ness to the awe­some and mighty first fruit of this sim­ple main method:

-- Main.hs
import List

-- List.hs
module List where

In­cred­i­ble. Inim­itable. Phe­nom­e­nal. All jokes aside, be­ing able to :r-eload to load the new file is con­ve­nient.

Now, we tru­ly be­gin:

import qualified Prelude

data List a = Cons a (List a)
            | Nil
              deriving (Prelude.Show)

Uh oh, our first cheat came ear­li­er than I ex­pect­ed. This one is just prag­mat­ic though, telling GHCi to use some cus­tom Show in­stance would be an­noy­ing if not im­pos­si­ble. We’ll do the same when we make a much bet­ter Show im­ple­men­ta­tion in a bit.

We can Construct our first list by hand:

ƛ Cons 4 (Cons 1 (Cons 3 Nil))
Cons 4 (Cons 1 (Cons 3 Nil))

This is a pain though, so let’s make our own ver­sion of the : op­er­a­tor.5

It’s real­ly not com­pli­cat­ed, as it’s just a con­ve­nience:

(-:) :: a -> List a -> List a
a -: as = Cons a as

infixr 5 -:

Note the infixr 5 -:, this lets us chain it in a nice way:6

ƛ 1 -: 2 -: 3 -: Nil
Cons 1 (Cons 2 (Cons 3 Nil))

Much nicer, but let’s also add a method to con­vert to and from lists for con­ve­nience sake:7

fromList :: [a] -> List a
fromList []     = Nil
fromList (x:xs) = x -: fromList xs

toList :: List a -> [a]
toList Nil         = []
toList (Cons x xs) = x : toList xs

Ah, mul­ti­ple equa­tions make re­cur­sion so much more ap­proach­able. Love­ly. We can check that these are the in­verse of each oth­er:

ƛ fromList [1, 2, 3]
Cons 1 (Cons 2 (Cons 3 Nil))
ƛ toList it
[1,2,3]

Per­fect.

Map­ping is sim­i­lar­ly ob­vi­ous:

map :: (a -> b) -> List a -> List b
map f Nil         = Nil
map f (Cons x xs) = f x -: map f xs

Once again pat­tern match­ing shows its strength with re­cur­sion, es­pe­cial­ly with such an ob­vi­ous base case. We can now make some tru­ly hor­rif­ic con­struc­tions with just a lit­tle bit of code:

ƛ let eugh n = n -: n -: n -: Nil
ƛ map eugh (map eugh (map eugh (Cons Nil Nil)))
Cons (Cons (Cons (Cons Nil (Cons Nil (Cons Nil Nil))) (Cons (Cons Nil (Cons Nil (Cons Nil Nil))) (Cons (Cons Nil (Cons Nil (Cons Nil Nil))) Nil))) (Cons (Cons (Cons Nil (Cons Nil (Cons Nil Nil))) (Cons (Cons Nil (Cons Nil (Cons Nil Nil))) (Cons (Cons Nil (Cons Nil (Cons Nil Nil))) Nil))) (Cons (Cons (Cons Nil (Cons Nil (Cons Nil Nil))) (Cons (Cons Nil (Cons Nil (Cons Nil Nil))) (Cons (Cons Nil (Cons Nil (Cons Nil Nil))) Nil))) Nil))) Nil

This does demon­strate the need for a nicer show in­stance, es­pe­cial­ly when com­pared to the stan­dard list:

ƛ let meh n = n : n : n : []
ƛ Prelude.map meh (Prelude.map meh (Prelude.map meh [0]))
[[[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]]]]

Much less in­tim­i­dat­ing.

How should we ap­proach mak­ing a Show in­stance though? I hate to do this, but for the sake of brevi­ty I’m ex­plic­it­ly im­port­ing show and Show:

With that out of the way, let’s start by ob­serv­ing that we can ob­vi­ous­ly only show a list of showable things, so that gives us the first part of our type sig­na­ture:8

show :: Show a =>

Then this show func­tion can take a list of Showables and, well, show them! Which leaves us with:

show :: Show a => List a -> String
show = undefined

But wait! Un­de­fined is part of the stan­dard pre­lude, and isn’t re­al­is­ti­cal­ly de­fin­able in a nice way for us.

It is re­al­is­ti­cal­ly, nay, triv­ial­ly, de­fin­able in a very bad way for us though:

undefined = undefined

Oh. Oh no. This ab­solute­ly works. This is be­cause of a mas­sive, gap­ing hole in the Haskell type sys­tem, which, like most oth­er gap­ing holes in foun­da­tion­al sys­tems, doesn’t real­ly mat­ter at worst and is more of­ten than not very use­ful.

SOME­WHAT PER­TI­NENT DI­GRES­SION 1: The bot­tom type ⊥#

The bot­tom type is a type-lev­el rep­re­sen­ta­tion of a com­pu­ta­tion that fails or nev­er ends.10

This is ob­vi­ous­ly ap­plic­a­ble to real pro­grams, some­times things crash and burn, and it’s of­ten even ex­pect­ed that a pro­gram won’t halt (think a serv­er). Since it’s ex­treme­ly dif­fi­cult, if not im­pos­si­ble, to tell if some­thing like that will hap­pen,11 bot­tom (⊥) is a mem­ber of every type.

Again, this isn’t a ma­jor prob­lem in prac­tice, and is of­ten ben­e­fi­cial, but it does mean your pre­cious Bools, for ex­am­ple, are, well.

END OF DI­GRES­SION#

Our undefined can nev­er com­plete, as it re­curs­es un­con­di­tion­al­ly, which means it’s ⊥ which means it’s every type, hooray!

Now we can sketch out our show im­ple­men­ta­tion:12

showList :: Show a => List a -> String
showList xs = toList
           (concat
           (map fromList
           (intercalate ", "
           (map show xs))))

And the nec­es­sary (skele­ton) de­f­i­n­i­tions to make this ac­tu­al­ly com­pile:

intercalate :: a -> List a -> List a
intercalate = undefined

concat :: List (List a) -> List a
concat = undefined

In­ter­ca­late is a weird word ob­vi­ous­ly, but once you get used to it you re­al­ize it’s pret­ty much per­fect. The dic­tio­nary de­f­i­n­i­tion tells you pret­ty much all you need to know, it just means putting some­thing be­tween oth­er stuff. This is per­fect for putting com­mas be­tween every­thing. The most straight­for­ward way that I’ve found to recre­ate intercalate13 is to ap­ply the clas­si­cal func­tion­al pro­gram­ming 1337 strat of solv­ing a sim­pler prob­lem first, name­ly adding an el­e­ment be­fore every el­e­ment of a list.

Here’s just that:14

appendBeforeAll :: a -> List a -> List a
appendBeforeAll a Nil         = Nil
appendBeforeAll a (Cons x xs) = a -: x -: appendBeforeAll a xs

This is def­i­nite­ly com­plex enough to war­rant test­ing real quick:

ƛ appendBeforeAll 99 (fromList [1,2,3,4,5])
Cons 99 (Cons 1 (Cons 99 (Cons 2 (Cons 99 (Cons 3 (Cons 99 (Cons 4 (Cons 99 (Cons 5 Nil)))))))))
ƛ appendBeforeAll 99 Nil
Nil

Now that we’ve solved the easy prob­lem, we can solve the hard prob­lem! Isn’t func­tion­al pro­gram­ming great?

Evan­ge­lism aside, here’s the ac­tu­al in­ter­ca­late func­tion:

intercalate a Nil         = Nil
intercalate a (Cons x xs) = x -: appendBeforeAll a xs

This is an os­ten­si­bly round­about so­lu­tion that is ul­ti­mate­ly very in­tu­itive (for me at least). It’s like mak­ing a sand­wich from two pieces of bread that are on top of each oth­er: You take off the top slice, put in the fill­ing, then put the top slice back on. It would be much hard­er, al­beit far from im­pos­si­ble, to do that with­out tak­ing off the top slice of bread.15

Now, be­fore we do concatena­tion of a List of Lists, we should fig­ure out how to con­cate­nate just two lists!

Let’s start with the easy case: Con­cate­nat­ing an emp­ty list to, well, any­thing:16

(++) :: List a -> List a -> List a
Nil ++ bs = bs

What to do next is less ob­vi­ous, so let’s go for an­oth­er sim­ple case even though it seems like it’s not real­ly go­ing any­where:

(Cons a Nil) ++ bs = a -: bs

Adding a sin­gle­ton17 list to an­oth­er list is just a round­about cons op­er­a­tor! But wait, won’t our con­cate­na­tion op­er­a­tor also even­tu­al­ly re­turn Nil? That means we can re­duce our de­f­i­n­i­tion to:18

(++) :: List a -> List a -> List a
Nil         ++ bs = bs
(Cons a as) ++ bs = a -: as ++ bs

infixr 5 ++

We quick­ly ver­i­fy that it works:

ƛ (fromList [1,2,3]) ++ (fromList [4,5,6])
Cons 1 (Cons 2 (Cons 3 (Cons 4 (Cons 5 (Cons 6 Nil)))))
ƛ (fromList [1,2,3]) ++ Nil
Cons 1 (Cons 2 (Cons 3 Nil))

We could de­fine concat now rel­a­tive­ly eas­i­ly, but, for an el­e­gant de­f­i­n­i­tion, we need one last thing: foldr.

foldr turns a list of val­ues into a sin­gle val­ue by re­peat­ed­ly ap­ply­ing a func­tion to the head of the list and an ac­cu­mu­la­tor. The type sig­na­ture is a bit scary, but it’s not that bad:

foldr :: (a -> b -> b) -> b -> List a -> b

If there are no el­e­ments we ap­ply f to the accumu­la­tor zero times, which just means re­turn­ing the accumu­la­tor un­changed:

foldr f acc Nil = acc

Oth­er­wise we ap­ply foldr to the head of the list and the re­sult of foldring the rest of the list:

foldr f acc (Cons x xs) = f x (foldr f acc xs)

And now for that sweet, sweet pay­off:

concat :: List (List a) -> List a
concat = foldr (++) Nil

Ah, point­free, how I’ve missed you.19

And with that the showList func­tion ac­tu­al­ly works!

ƛ showList (fromList [1,2,3])
"1, 2, 3"
ƛ showList Nil
""

And, if we re­move the deriving (Prelude.Show) line, we can make that how GHCi will print our lists from now on:

instance Show a => Show (List a) where
    show = showList

Al­though there’s a tiny prob­lem:

ƛ fromList [1,2,3]
1, 2, 3
ƛ Nil

Show­ing noth­ing for Nil kind of makes sense, but is bound to cause prob­lems. Let’s fix that by sur­round­ing the list with some cool brack­ets: ⟦⟧

We’d might as well make a gener­ic func­tion just in case we want to use some­thing like this lat­er:

singleton :: a -> List a
singleton x = Cons x Nil

surround :: a -> a -> List a -> List a
surround l r xs = l -: xs ++ singleton r

We can add yet more nest­ed paren­the­ses:20

showList :: Show a => List a -> String
showList xs = toList
             (concat
             (map fromList
             (surround "⟦" "⟧"
             (intercalate ", "
             (map show xs)))))

With that, we’re done with our list for now. We’ll def­i­nite­ly add more, such as a length op­er­a­tor when we have our own in­te­gers, but this is pret­ty great.

ƛ fromList [1,2,3]
1, 2, 3
ƛ map (Prelude.+ 1) it
2, 3, 4
ƛ foldr (Prelude.+) 0 it
9
ƛ "remember this?"
"remember this?"
ƛ let eugh n = n -: n -: n -: Nil
ƛ map eugh (map eugh (map eugh (singleton 0)))
⟦⟦⟦⟦0, 0, 0, 0, 0, 0, 0, 0, 0⟧⟧, ⟦⟦0, 0, 0, 0, 0, 0, 0, 0, 0⟧⟧, ⟦⟦0, 0, 0, 0, 0, 0, 0, 0, 0⟧⟧⟧⟧

Won­der­ful.

Num­bers#

Num­bers are real­ly hard.

More pre­cise­ly, the way I’m go­ing to be do­ing num­bers is real­ly hard. Num­bers and math in pro­gram­ming lan­guages nor­mal­ly have a very in­ti­mate re­la­tion­ship with the CPU be­cause they are used so dras­ti­cal­ly of­ten.

How­ev­er, I’m not try­ing to make a func­tion­al21 pre­lude, I’m try­ing to make a func­tion­al22 one.

A Re­cur­sive Datatype for Num­bers#

-- Main.hs
import Num
-- Num.hs
module Num where

import           Prelude (Show, String)
import qualified Prelude

data Int = Succ Int | Pred Int | Zero
    deriving (Show)

Now that I’ve ripped off the gener­ic self-ad­he­sive mini-ban­dage, let’s talk about the log­ic be­hind this… thing. Is us­ing a re­cur­si—No, of course not, this is of sole­ly ed­u­ca­tion­al val­ue.24

This datatype is a vari­a­tion of Peano num­bers that al­low for signed­ness, which I feel has some nice ben­e­fits to both us­abil­i­ty and ease of im­ple­men­ta­tion.25 Take the suc­ces­sor and pre­de­ces­sor func­tions:26

That’s right, Haskell datatypes make sneaky func­tions, so our suc­ces­sor and pre­de­ces­sor func­tions (hence­forth short­ened as succ and pred) are al­ready de­fined!

ƛ Succ Zero
Succ Zero
ƛ Pred it
Pred (Succ Zero)

This nice­ly demon­strates a prob­lem that we can nice­ly solve: re­dun­dan­cy. Neg­a­tive one and pos­i­tive one can­cel out, leav­ing noth­ing be­hind:

reduce :: Int -> Int
reduce (Succ (Pred n)) = reduce n
reduce (Pred (Succ n)) = reduce n

And zero re­mains zero:

reduce n = n

This func­tion as it is now will re­duce our num­bers, but not as much as they can be re­duced. Take the fol­low­ing sce­nario:

Succ (Succ (Pred Zero))

There’s clear­ly some can­celling that can hap­pen.27 Un­for­tu­nate­ly, be­cause the be­gin­ning can­not be re­duced, the func­tion stops too ear­ly, miss­ing out on some juicy (po­ten­tial­ly in­fi­nite) re­duc­tion. This prob­lem, like many oth­er prob­lems in life, can be solved with more re­cur­sion.28

We re­place the reduce n = n with:

reduce (Pred n)        = Pred (reduce n)
reduce (Succ n)        = Succ (reduce n)
reduce Zero            = Zero

Which will ea­ger­ly chomp through the num­ber, deft­ly han­dling the pre­vi­ous ex­am­ple, and even a slight­ly more patho­log­i­cal case:

ƛ Succ (Succ (Pred Zero))
Succ (Succ (Pred Zero))
ƛ reduce it
Succ Zero
ƛ Pred (Pred (Succ (Pred (Succ Zero))))
Pred (Pred (Succ (Pred (Succ Zero))))
ƛ reduce it
Pred Zero

These paren­the­ses are get­ting an­noy­ing. Let’s make a fromInt method to re­duce the has­sle.


  1. Yes, I know it’s a lamb­da ↩︎
  2. I’m go­ing to use ƛ as my prompt be­cause it’s like a de­riv­a­tive of Haskell’s logo1 (and it looks nice :]) ↩︎
  3. When. ↩︎
  4. Once again that’s very much­worth­while for per­for­mance’s sake, but not what I’m real­ly look­ing for. This is such a com­mon theme that I’m not even go­ing to both­er men­tion­ing it in the fu­ture (un­less it’s par­tic­u­lar­ly fun­ny) ↩︎
  5. I was sur­prised to find out that this is ac­tu­al­ly a builtin of the lan­guage, al­though that is con­sis­tent with the fan­cy pat­tern match­ing (eg. (x:xs)) be­ing built in. ↩︎
  6. Ab­solute­ly stolen from https://hack­age.haskell.org/pack­age/base-4.17.0.0/docs/src/GHC.Base.html#line-140 ↩︎
  7. Lists are a builtin, not a pre­lude thing. If they weren’t, we could just cheat; cheat­ing isn’t ac­tu­al­ly a big deal if you haven’t no­ticed, some­times it’s sim­ply nec­es­sary. ↩︎
  8. That => sep­a­rates type con­straints from the ac­tu­al type of the func­tion, so this es­sen­tial­ly reads as “For all types a where a is Show ↩︎
  9. The end­less gods, with­out time, with­out mor­tal­i­ty are de­spi­ca­ble in their bound­less for­ti­tude. They think them­selves so far above the pet­ty squab­bles of us mor­tals, but it is mere­ly the ul­ti­mate delu­sion. Through­out hu­man his­to­ry they have dis­tort­ed us into think­ing that mor­tal­i­ty, death, fin­i­ty is the ul­ti­mate de­ple­tion. But. But. They are wrong. They know they have wrong. They have al­ways been wrong. Our reck­on­ing with mor­tal­i­ty may be some­what present through­out our en­tire lives, but there is a sin­gle mo­ment where we ful­ly con­front it. And then it is fin­ished. Their reck­on­ing with im­mor­tal­i­ty, life, in­fin­i­ty will nev­er, can nev­er, end. And it grows.

    The end­less fin­i­ty of the uni­verse, time and space, is not a game won by the last man stand­ing.
    We should be glad to die.
    They envy us.
    They would give any­thing to be us.
    No mat­ter how much they may say oth­er­wise to us.
    No mat­ter how much they may say oth­er­wise to them­selves.
    Be­cause see­ing the end of our own in­di­vid­ual lives is so, so much bet­ter than see­ing the end of All. ↩︎

  10. Which as we all know is worse than fail­ure.9 ↩︎
  11. Telling whether a giv­en pro­gram will halt is fa­mous­ly kin­da hard. ↩︎
  12. Yes, this could be made much bet­ter by us­ing ., we’ll get to that in the next chap­ter. ↩︎
  13. Say that ten times fast. ↩︎
  14. (x:xs) is a very com­mon to thing to see in Haskell, we’ve al­ready seen it a cou­ple times be­fore in this post. I like to think of xs as ex­cess :) ↩︎
  15. That anal­o­gy was very des­per­ate but I think it was sur­pris­ing­ly ef­fec­tive :]. ↩︎
  16. Steal­ing the name from the stan­dard pre­lude, as is par for the course by now. ↩︎
  17. The word sin­gle­ton fol­lows a com­mon math pat­tern of hav­ing a very gen­er­al de­f­i­n­i­tion with a lot of ter­mi­nol­o­gy, that, while use­ful, is un­nec­ces­sar­i­ly con­fus­ing in most cas­es. For our pur­pos­es, a sin­gle­ton is a list with one el­e­ment. ↩︎
  18. Again, the mag­ic in­fixr is ab­solute­ly stolen from https://hack­age.haskell.org/pack­age/base-4.17.0.0/docs/src/GHC.Base.html#line-140 ↩︎
  19. We’ll have a lot more op­por­tu­ni­ties for point­free el­e­gance in chap­ter two. ↩︎
  20. If this is ap­peal­ing to you for some rea­son you might like LISP. ↩︎
  21. De­f­i­n­i­tion 2 ↩︎
  22. De­f­i­n­i­tion 5/6 ↩︎
  23. It would inar­guably be much hard­er (for me) to im­ple­ment; I at least know where to start with the datatype ap­proach. ↩︎
  24. Al­though learn­ing how to use com­pil­er in­trin­sics (or what­ev­er it would ac­tu­al­ly take) would ar­guably be more ed­u­ca­tion­al.23 ↩︎
  25. It’s im­por­tant to re­mem­ber that signed num­bers are of­ten not the best tool for the job. For ex­am­ple, what is the neg­a­tive third el­e­ment of a list, well, ac­tu­al­ly that gives me an idea. ↩︎
  26. Fan­cy words for adding and sub­tract­ing one, re­spec­tive­ly (but not in a dirty im­per­a­tive way). ↩︎
  27. Here’s a tip for re­duc­ing these num­bers in your head: take the num­ber of Succs and sub­tract the num­ber of Preds. ↩︎
  28. Ha. ↩︎