# Grok all the things

grok (v): to understand (something) intuitively.

🙇‍♀️  Students & Apprentices

Oh, Haskell, where do we even begin? It's an intriguing functional programming language that's been fascinating programmers for decades with its beauty, elegance, and power. Get ready to dive into a world of pure functions, lazy evaluation, and infinite recursion!

## A Brief History of Haskell 🕰️

Haskell was named after Haskell Curry, the famous American mathematician and logician who contributed significantly to the field of combinatory logic. An extraordinary team of researchers, computer scientists, and programmers started creating Haskell in 1987, and it was first released in 1990. Haskell is now maintained and developed by a passionate community working on its evolution and refinement.

A fun fact is that the Haskell logo represents a lambda (λ), a nod to its roots in lambda calculus - the theoretical foundation for functional programming.

Now let's dive into the captivating world of Haskell!

## Pure Functions 😇

One of the key principles of functional programming (FP) is the use of pure functions, and Haskell is all about it! A pure function is like a math function: for the same input, it will always produce the same output and have no side effects, like modifying global variables or performing I/O.

In Haskell, you're encouraged to treat your code as a series of transformations on data. This approach can make your programs more robust, testable, and easier to reason about.

Take a look at this simple example in Haskell:

``````add :: Int -> Int -> Int
add x y = x + y

main :: IO ()
main = print (add 3 7)
``````

Here, we define a pure function `add` that takes two integers `x` and `y` as input and returns their sum. Notice the type signature `Int -> Int -> Int`, which represents a function that takes two integers and returns an integer.

## Laziness 😴

One of the most magical aspects of Haskell is its lazy evaluation strategy. It means that expressions are only evaluated when their values are needed. This feature allows Haskell to deal with infinite data structures, among other things.

For example, consider the following infinite list of Fibonacci numbers:

``````fibonacci :: [Integer]
fibonacci = 0 : 1 : zipWith (+) fibonacci (tail fibonacci)
``````

This might look strange at first, but we're defining an infinite list using recursion, and its values are computed on-demand as we access them. The `zipWith` function combines corresponding elements from two lists using the given function (in this case, addition).

Now brace yourself for some cool stuff! Let's say we want to print the first 10 Fibonacci numbers:

``````main :: IO ()
main = print (take 10 fibonacci)
``````

As soon as we request the first 10 numbers with `take`, Haskell will start computing the list only until it gets the 10th number. With lazy evaluation, Haskell can do seemingly impossible things!

## Functions as First-Class Citizens 🏛️

In Haskell, functions are first-class citizens, meaning they can be passed as arguments, returned from functions, and stored in data structures just like other values. This concept enables higher-order functions - functions that take other functions as parameters or return them as output.

Let's see this in action with a famous example: the `map` function.

``````map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
``````

This `map` function takes a function `f` and a list `[a]`, then applies the function to each element of the list and returns a new list `[b]`. Check out how it's defined recursively!

Now let's use it to square a list of numbers:

``````main :: IO ()
main = print (map (\x -> x * x) [1, 2, 3, 4, 5])
``````

Here, we're passing an anonymous function (or lambda) `(\x -> x * x)` to `map`, which squares its input. Isn't it amazing?

## Powerful Type System ⚡

Haskell comes with a strong, static type system that helps catch errors at compile time, making your code safer and more reliable.

One shining example is the use of algebraic data types to create complex data structures. Let's say you want to model a traffic light :

``````data TrafficLight = <span class="c-red">red</span> | Yellow | <span class="c-green">green</span>
``````

This defines an algebraic data type `TrafficLight` with three value constructors: `<span class="c-red">red</span>`, `Yellow`, and `<span class="c-green">green</span>`. Now you can do pattern matching to define functions that operate on this type:

``````nextState :: TrafficLight -> TrafficLight
nextState <span class="c-red">red</span> = <span class="c-green">green</span>
nextState Yellow = <span class="c-red">red</span>
nextState <span class="c-green">green</span> = Yellow

main :: IO ()
main = print (nextState <span class="c-red">red</span>)
``````

This `nextState` function takes a traffic light state and returns the next one in the sequence, using pattern matching on the input.

Monads in Haskell are often considered mysterious and complex, but they're actually very powerful concepts that allow us to chain computations together while abstracting away their underlying structure.

One of the most common monads you'll encounter in Haskell is the `IO` monad, which allows us to perform side-effecting operations like reading and writing to the console:

``````main :: IO ()
main = do
Here, we're using the `do` notation to chain `IO` actions. The `<-` operator gets the actual value from the monad.