Functional Programming Journal
Foreword
It is not only the violin that shapes the violinist, we are all shaped by the tools we train ourselves to use, and in this respect programming languages have a devious influence: they shape our thinking habits. - Dijkstra
At the time of writing this foreword (9/6/2022), I have functionally programmed ~200 hrs (mainly, regurgitating and practicing this). I love it, it’s a challenge, it makes me think clearer and feel more powerful. Two months ago, I started dabbing in Haskell a bit and am 10 chapters into this. As Euclid deemed that “there is no loyal road to geometry”, the modern FP wizards deem that there is “no loyal road to FP”. But, I still want to serve this community with my personal learning notes. Perhaps you, my reader, will stumble upon this journal years after my initial post, and at least find some comfort in that there are others after the same destination, who followed through and prevailed (hopefully).
I want to maintain the entire journal in this single post. I shall debrief daily, but I’ll compile the debriefs each month so that this place doesn’t get too cluttered.
Edit (11/28/2022): I am not going to compile the journals at the end of each month. I think a compilation will dull the sentiments of the journal! However, to avoid clutter, I’ll open up a new journal post every few months.
September 2022
6th
foldr, foldl, fold'
: foldr can short circuit. foldr, foldl are lazy, stack overflow prone. foldl’ is strict (opposite of lazy), hangs in infinite loop because no stack overflow.
7th
HPFFP pg. 590-642: Interesting Product
, Sum
types (data Sum a b = First a | Second b
). First time coming across kinds, cardinality (despite being familiar with sum and product type).
8th
HPFFP pg. 643-681: Function type is the exponent operator (in regards to sum and product types). Higher kinded types are similar to higher order functions. Lists are polymorphic.
9th
First half of Input and Output. Solved 5 basic codeforces problems according to this list left. Biggest lesson from doing these exercises: Type can be inferenced using knowledge of the next function invoked, this is how read
can be written to not use explicit casts; interact $
.
10th
HPFFP pg. 681-690. Started studying binary trees, yet to comprehend Foldable instance for Tree implementation:
foldr f acc Leaf = acc
foldr f acc (Node l x r) = foldr f (f x (foldr f acc r)) l
Tomorrow I want to understand everything in the previously attached linked deeply.
11th
Understood yesterday’s snippet: To fold a tree, recursively fold its left subtree with the accumulator value being the result of the input function applied to the current root node’s value and the result of folding the right subtree.
Started doing HPFFP Ch.11 exercises, stuck on Ciphers problem. Watched 90 minutes of this: How a custom Parser type (which has instances for Functors and Applicatives) can parse json (also the topic of HPFFP Ch.24). I’ll refrain from finishing the entire video, need to build up to understanding everything.
12th
Used zipWith
to iterate two Foldables at once to complete the cipher (thanks to my python knowledge); Learned as-patterns
to bind original variable while pattern matching it.
14th
HPFFP Ch.15 reading. Types can have more than one monoidal operation.
15th
HPFFP Ch.13 reading and exercises: Built hangman!
16th
Read almost all of HPFFP Ch.14 on testing, still internalizing Arbitrary
and Gen
. Solved code forces 2A in haskell, used Data.Map
for the first time.
16-27th: Busy with other stuff. Read half of the chapter on Functors.
28th: Lisp s-expressions. Stumbled upon this because I’m working on a call graph generator which uses tree-sitter, a parser with a query language formatted with s-expressions.
October 2022
5th: Q1 - Q4 of 99 problems. Ignore first argument in function application by using const. Ignore second argument with a const . (+1)
. So damn cool.
6th: Went back to foldr, foldl, fold'
. I learned that seq
can be used to enforce strictness in the lazy realm of haskell.
15th: (fmap . fmap)
penetrates the value of the second Functor. More can be chained. I was able to trace the type of this expression, but man, I don’t have much intuition of it.
16th: Finished HPFFP Functor Chapter. Instead of pattern matching on Just
and Nothing
, just use a single fmap
! Understand functors more, like why fmap
only hits the second argument in Either
, tuples, etc.
17th: HPFFP p.1052-p.1067. Obtained basics of Applicative
. It was tough writing the pedagogical Applicative
instance for Constant x
, had to look up both how to write pure
and <*>
, it all makes sense now though.
18th-22nd: HPFFP p.1067-1206 (currently on Monad exercises). Built a simple tic tac toe. Internalized <$>
, <*>
, >>=
. I need, however, a deep understanding of “algebras that aren’t other algebras”. In other words, concrete examples of say, Applicative instances that aren’t Monad instances.
28th: Read over how to use quickcheck in HPFFP again, did some Monad exercises.
29th: HPFFP p.1212-1258: Learned to build a URL shortener! Also TIL replicateM 5 [1,2,3]
effectively generates all length 5 permutations with elements from [1,2,3]
. This is because replicateM
performs the monad 5 times, and list monads combine on mappend.
30th-31st: HPFFP Ch20 Foldable and Ch21 Traversable (p.1259-1314). Fun and easy.
November 2022
1st: HPFFP Ch22 Reader Monad. Tbh, this article offered a great explanation. There is, more to the reader monad than resolving “prop drilling” (term used by the ReactJS community), but it is one of its greater uses.
2nd: I found that similar to how fmap . fmap
targets a value beneath nested functors, foldMap . foldMap
targets a value beneath nested foldables
. The concept might be understood quicker if one were to just write fmap (fmap f)
and foldMap (foldMap f)
though. The (.)
syntax, while terse, doesn’t imply nestedness. However, I’m but a novice, I think stronger readers can understand quickly that fmap
can be seen as (a->b) -> (f a -> f b)
, so the second fmap
of (fmap . fmap)
is the function the first fmap
accepts, which is functorial (f a -> f b)
.
3rd: HPFFP Ch 25 and partial Ch 26. I’m getting a hang of monad transformers thanks to this. What I understand is that MaybeT
return Nothing
in its >>=
when the first part’s nested Maybe is indeed a Nothing
. What I still need are more examples. It seems like MaybeT
, in the stackoverflow example, solves the staircase problem because the Maybe-ness is embedded in the >>=
, and thus embedded between the lines of a do
block. This prevents the need to pattern match on Maybe
, and thus prevents the staircase problem.
4th: Watch this little gem on how IO is defined using the state monad. Haven’t understood everything about it yet. I also traced through a state monad in this video, also still trying to understand it.
5th-9th: Went through the Turnstile State example. In addition to the essence of state, I also dabbled in foldM
, mapM
, filterM
, and sequence
.
11th: Wrote the instance for the monad transformers ReaderT
and StateT
. Turns out, I can’t write an Applicative
for StateT
unless the inner higher typed variable is a Monad
. This is because, to get from s -> m (s, a->b)
and s -> m(s, a)
to s -> m(s, b)
, I can apply the s
to the first expression to get m (s, a->b)
, but then, to get the s
out of this expression in order to apply it to the second expression, m
must be a Monad.
12th-13th: More on monad transformers, read through the MonadTrans section in HPFFP but didn’t really internalize much, so I plan to read this.
14th: The reason we do MaybeT $ do ...
when defining the monad instance for MaybeT
is that do
must be in the other monad, m
’s context, and not in MaybeT m
’s (because it MaybeT
monad is yet to be defined). “There is no magic formula to create a transformer version of a monad; the form depends on what makes sense in the context of its non-transformer type.” We can write liftM with the bind or do notation.
15th: Read this little piece on monad transformers. Now reading FP Complete’s piece on it. The foldTerminate
function utilizing Either monad is something I haven’t seen before. I’m going to carefully trace through and unwrap it tomorrow.
18th: Read the StateEither
example of FP Complete’s piece of monad transformers.
19th: I am getting a hang on monad transformers. The StateEither
’s Applicative
and Monad
instances required no explicit usage of State
functions, it just uses return
and >>=
, which all monads have. So really, many monads can wrap Either
.
21st: Summary of the FP Complete’s article so far:
step 1: Observe that we can write foldTerminate whose fold f returns EitherWe can then write sumTillNegative, with negative case returning (Left acc)
step 2: Avoid explicitly patterning matching on Either by using Either's monad and do notation.
step 3: Notice that fold can be implemented with the State monad. So, foldTerminate can be implementedwith a State monad whose outputs were Either.
step 4: We make a StateEither type and write the algebra instances; we write the helper methods
step 5: We notice that it is possible to write the algebra instances with methods that all monads have (>>=, return).
step 6: We make EitherT and write algebra instances and an MonadTrans instance for it; we write the helper methods
step 7: The helper methods, like modify, do not need to be defined on Either. So we write modifyM that uses the polymorphic lift.
22nd: Exercise #1 on the FP Complete article
23rd: Took me 2 hrs to write this for the article’s Exercise #2:
foldTerminateM :: Monad m=> (b -> a -> m(Either b b)) -> b -> [a] -> m b
foldTerminateM f accum (x:xs) = fmap (either id id) $ runExceptT $ do
b <- ExceptT $ f accum x
lift $ foldTerminateM f b xs
foldTerminateM f accum [] = return accum
I was getting confused at first tackling this problem since I was using the wrong “lifting action”. Specifically, I was doing return $ foldTerminateM f b xs
instead of lift $ foldTerminateM f b xs
.
26th: Exercise 3. Going to take a detour to study Lenses since exercise 4 depends on it.
28th: 20 minutes into SJP on Lenses, implemented my own Bad Lens example.
29th: I decided to read some real Haskell code, think I’ve been in the ivory tower for too long! To start, I’m reading 2048 written in Haskell with the TUI library bricks. Two nuggets I learned thus far: 1) Use forkIO
to start a background thread. 2) Use uncurry
to apply a tuple as the first two args to a function. Overall, it’s very surprising how easy it is to write 2048 with Haskell in a framework. bricks
allows for a very declarative style of programming, where you predefined all your event handlers and drawing functions in one App
object. When I’m experienced enough, I will read bricks
.
30th: Started working on a tic-tac-toe clone, which I affectionately named hic-hac-hoe
. I am using bricks
, so this is TUI, and so I envision users moving around the highlight block with the arrow keys, pressing enter
to make a move. I have already implemented a dummy cell with text “highlight”, that moves around in the tic-tac-toe grid on arrow key inputs. The code is very similar to the 2048 haskell clone.
December 2022
1st, 2nd:
3rd: I started looking around for networking libraries. The author of bricks
recommended zeromq-haskell
to me, so I probably will look a lot into it, but it doesn’t have much documentations since it is a binding.
4th: I browsed for multiplayer haskell games. Unfortunately, most of them use web server frameworks like Yesod
, scotty
. which I don’t need since I just need some simple messaging relaying. I am going to follow haskell wiki’s Network.Socket
only “implement a chat server” tutorial first, and then maybe dig into zeromq
if I think I need more.
5th: I took a detour and did a thought experiment on how to model a call graph generator application in Haskell. I got stuck, so I asked. Also, I do not plan to study much Haskell in the coming 2 weeks. I have finals and research papers to read! During the winter break, however, I plan to basically Haskell every waking hour and also start working through TAPL.
14th: I haven’t touched FP in the last 10 days due to finals. However, I have learned in the last 10 days that becoming acquainted with PL jargons serves me better in the longer run than working through something specific like TAPL. So, during my winter break, I’ll instead work with PLP (Programming Language Pragmatics), which is, actually, the current textbook of my PL class.
25th: Merry Christmas! I began digging into vty
, a ncurses
alternative written in Haskell. vty
is a precursor of brick
, which I plan to dig into in the future.
February 2023
7th, 8th: I’ve taken a huge break from FP due to school but now I’m back. I began digging into the Parsec library and learned the basics of Parsec via this article. Since I eventually want to produce a code-base-deep-dive video on Parsec, I am reading earlier papers on Monadic Parser Combinators to gain a thorough understanding. I am 10 pages into Hutton’s work. The most important improvement that Parsec since made is better error handling and performance.
Apr 2023
From Feb 8th to Apr 2nd:
- Read A History of Haskell: being lazy with class
- Read It’s Easy As 1, 2, 3
- Inspired me to read the Semantic Algebras chapter of Denotational Semantics, A Methodology for Language Development
- For my undergrad research on Interpreting Language Models for Functional Programming, I defined a taxonomy for Haskell (mapping Haskell grammar rules to general categories, e.g.,
algebraic data types
belongs to the categorytype system
). In defining this taxonomy, I came across many Haskell extensions that was complex for me to organize. Thankfully, I discovered Thinking with Types, Type-Level Programming in Haskell. I am only one chapter in, but I plan to finish this book.
Apr 3rd: A few pages of Thinking in Types (I abbreviate as TiT from now). Fully applied (saturated) TypeClasses always end in Constraint
, this single fact seems to be the missing piece for me to internalize the non-extended Haskell’s type system. Previously, I learned from HPFFP
that a type constructor like Maybe
has kind * -> *
, but I think I never remembered these stuff well because I didn’t internalize where TypeClasses fit in the whole scheme.
May 8th: Sorry! Another big skip. I didn’t do much last month. Today, I reread TiT Chapter 1, took notes, and did the exercises. The big idea I learned is that the Curry-Howard correspondence/isomorphism allows us to analyze mathematical theorems through the lens of functional programming. We get surprising results, for example, somehow, the power of power rule corresponds to currying.
May 9th: Reread TiT Ch2.1 and 2.2. This time around, I’ve cleared up many concepts, for example
(->)
is higher kind- Exact difference between
TYPE
(*
) and type As last time, I got stuck when reading the admin token example. But I did more digging into the example and learned: Proxy
is a type constructor whose only type variable is poly-kinded. That’s how it’s able to pass any type around.- One usage of
forall
is to allow for type variables to show up in data constructors but not in their type constructors.data Worker x y = forall b. Buffer b => Worker { buffer :: b, input :: x, output :: y}
- Scoped Type Variables allow free variables to be re-used in the scope of a function.
mkpair :: forall a b. a -> b -> (a, b) mkpair aa bb = (ida aa, bb) where -- ida :: b -> b would error! ida :: a -> a -- Refers to "a" in the type signature. ida = id
Yet, I still don’t understand the admin token example. I checked out TiT’s source repo and it didn’t have that part filled in. However, I did find more code around the TicTacToe example in Ch1 that taught me a few more things. I’ll skip this example for now.
May 10th: Finishes TiT Ch2. Learned about how lists and tuples behave in type-level programming. I don’t have the intuition down, but I kept querying GHCi
to get practice. Here’re some of my queries and results:
:set -XDataKinds
> :t []
[] :: [a]
> :k []
[] :: * -> *
> :k [Bool]
[Bool] :: *
> :k '[]
'[] :: [a]
> :t '[] -- throws error.
> :k '[ Bool ]
'[ Bool ] :: [*]
> :k '[ 'True ]
'[ 'True ] :: [Bool]
I stumbled half an hour into Ch3, couldn’t understand much. This textbook is not beginner friendly.
May 11th: Finished TiT Ch3-Ch4.2. Confused about the following two statements from the Ch3, they seem conflicting with each other. 1) Variance is a property of a type in relation to one of its type constructors. 2) The variance of a type T a
with respect to its type variable a
is … By the definition of 1), it seems off to say 2). I skip this for now. Coolest thing I learned is the positive/negative positioning trick to quickly determine a type’s variance.
Then, I read the FPComplete’s Covariance and Contravariance article and learned the mappings between Haskell typeclasses and variance (e.g., Profunctor
is a bifunctor where the first argument is contravariant and the second argument is covariant).
Onto Ch4 —
- I filled in a knowledge gap of
forall
from before. I knew thatforall
can be applied in ADT declaration. But when there’re applied to functions, they moreso expand the type variables’ scope to the entire function (in conjunction withXScopedTypeVariables
). XTypeApplications
allow us to apply types to polymorphic functions in the same way we can apply value arguments to functions.
May 12th: Finished TiT Ch4.3-Ch5. Learned about how to write a heterogeneous list and that the GADTs syntax is just syntactic sugar upon type equalities. So, I challenged myself to write HList
with the latter:
data HList (xs :: [Type])
= HNil
| forall t ts. (xs ~ (t ': ts)) => t (:#) HList ts
This allows for functions like
showBool :: HList '[_1, Bool, _2] -> String
Afterwards, I wrote the Eq
, Show
, and Ord
instances for HList
because GHC can’t derive GADTs instances well. To do this efficiently, I also learned to write the closed type family All
to fold [TYPE]
into a CONSTRAINT
.
May 13th: Finished TiT Ch6-Ch6.3.
Biggest takeaway: Universally quantified type variables are instantiated at the call site while existentially quantified type variables can be instantiated in the function definition. With existentially quantified type variables, we can take in a truly polymorphic functions, and then instantiate it if needed.
May 18th: TiT Ch6.3
6.4 started talking about the Continuation Monad. I’m can’t figure out the essence despite knowing how to write the instances. I looked for other sources, finally landing on this. The author suggests that to understand a Monad’s purpose, I must study what its Kleisli arrows do, something I don’t know much about.
So starting tomorrow, I’ll take a detour from TiT and into Category Theory 3.2: Kleisli category and the lectures before it. Then, I’ll finish the article, and then back to TiT.
May 19th: Category Theory Playlist 1.1-2.1
I dulged in 2 hours of Category Theory lecture today.
Bartosz Milewski is an excellent lecturer. I absorbed more about the epistemology of Category Theory and Physics more than Category Theory itself. I grasped the axioms of a category, understood an example category pertaining to programming, studied the category of sets, and learned about some relationships between sets and categories (isomorphism corresponds to function invertibility).
I wonder if the power of type level programming follows:
If I have a def that does:
def false(): return False
The return type can only be annotated as “Bool”, even though we know it can ever be only False.