Feelin' Functional
For a fair amount of time, I have been interested in functional programming, especially because of Lean, theorem proving, and formal languages in general.
But I did not really get into it for a long time, and the reason was pretty simple.
Functional programming looks intimidating.
No mutation, almost no side effects, no classes, and some of the craziest type signatures you will ever see. Stuff like this does not exactly make you feel welcome:
type Enumerate<N extends number, Acc extends number[] = []> = Acc["length"] extends N ? Acc[number] : Enumerate<N, [...Acc, Acc["length"]]>
type IntRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>
type T = IntRange<20, 300>And even for simple problems, the way you think changes a lot.
Take reversing a list. In Python, I would probably do something like this:
def reverseString(s: list[str]) -> None: if len(s) == 0 or len(s) == 1: return
start = 0 end = len(s) - 1
while start < end: s[start], s[end] = s[end], s[start] start += 1 end -= 1That is efficient, straightforward, and honestly just makes sense.
But now imagine you cannot mutate start or end. You also cannot mutate s. Now what? Think about it for a moment.
.
.
.
def reverseString(s: List[str]) -> list[str]: if not s: return [] return reverseString(s[1:]) + s[:1]and now for haskell
reverseString :: [a] -> [a]reverseString [] = []reverseString [x] = [x]reverseString (x:xs) = reverseString xs ++ [x]And a better version is that runs on O(n)
reverseList :: [a] -> [a]reverseList xs = go [] xs where go acc [] = acc go acc (x:xs) = go (x:acc) xsThat shift is what made functional programming interesting to me. You stop thinking about mutating your way to the answer and start thinking more in transformations.
That is where Haskell really started clicking for me.
Why I like Haskell
Section titled “Why I like Haskell”At first, Haskell looked kind of ridiculous to me. The syntax was unfamiliar, the type signatures looked absurd, and everyone on the internet talked about monads like that was supposed to make things less confusing.
But after using it more, I started realizing that a lot of what felt scary was really just precision.
One of my favorite things about Haskell is the type system. I genuinely love it. The types are not just there to reject your code. They help you write it.
Something like this already tells you a lot:
safeHead :: [a] -> Maybe asafeHead [] = NothingsafeHead (x:_) = Just xJust from the type, you already know this function might fail, and that failure is handled explicitly. I really like that. It makes the code feel honest. It kinda reminded me of units in Physics where the units/types guide me to the asnwer.
I also love typed holes. Being able to put a _ somewhere and let GHC tell me what type belongs there is so nice.
addOne :: Int -> IntaddOne x = _That sounds tiny, but it genuinely changes the experience of writing code. Instead of guessing what should go there, I can let the compiler guide me a bit. It feels less like fighting the language and more like working with it.
Monads
Section titled “Monads”Monads also get made to sound way scarier than they actually are.
I do not think of them as some magical functional programming thing you need a PhD to understand. I just think of them as a clean way to deal with context.
For example, Maybe handles failure:
safeDiv :: Int -> Int -> Maybe IntsafeDiv _ 0 = NothingsafeDiv x y = Just (x `div` y)And IO handles side effects:
main :: IO ()main = do putStrLn "What is your name?" name <- getLine putStrLn ("Hello, " ++ name)What I like is that all of this is explicit. If something can fail, I can see it in the type. If something does IO, I can see it in the type. Nothing is pretending to be pure when it is not.
Pure functions are just nice
Section titled “Pure functions are just nice”Another thing I like a lot is the emphasis on purity.
In many languages, effects are everywhere by default. Printing, reading input, mutating state, throwing exceptions, all of it gets mixed into the actual logic of the program. In Haskell, there is a much sharper separation between the pure part of your code and the effectful part.
I have grown to really appreciate that.
Pure functions are easier to trust. Same input, same output. No hidden state, no random mutation somewhere else, no weird behavior because things happened in the wrong order. That makes code easier to test, easier to reason about, and easier to revisit later.
Even a tiny function like this feels very clean:
squareAll :: [Int] -> [Int]squareAll xs = map (\x -> x * x) xsIt takes data in, transforms it, and gives data back. That is it. No hidden state. No surprises.
I think that discipline is one of the reasons functional programming feels so clean when it is done well.
Why it stuck with me
Section titled “Why it stuck with me”What I like most about functional programming is that it changed how I think even outside of Haskell.
I now care a lot more about smaller functions, less mutation, clearer data flow, and fewer hidden effects. I think more about types, composition, and modeling a problem well instead of just making something work.
So even when I am writing Python or TypeScript, I still feel that influence: I am definitely using zip, filter and map more from now on.
Functional programming definitely looked intimidating to me at first, and honestly I think that feeling is fair. But once I got over that initial wall, I started finding it not just useful, but actually really fun.
And that is pretty much why I like it.