main :: IO ()
A quest through the landscape of pure functional programming
A function that, given the same input, will always return the same output and produces no side effects. The cornerstone of referential transparency.
double :: Int -> Int
double x = x * 2
Values in Haskell cannot be modified after creation. There is no assignment, only binding. Data structures are persistent, sharing structure across versions.
let xs = [1,2,3]
let ys = 0 : xs -- [0,1,2,3]
Expressions are not evaluated until their values are needed. This enables infinite data structures and separates the description of computation from its execution.
take 5 [1..] -- [1,2,3,4,5]
-- The infinite list is never fully realized
The type of values that might not exist. A type-safe replacement for null that forces you to handle the absent case.
data Maybe a = Nothing | Just a
safeDivide :: Int -> Int -> Maybe Int
safeDivide _ 0 = Nothing
safeDivide x y = Just (x `div` y)
A value that is one of two types. Conventionally, Left holds errors and Right holds successes. A richer alternative to Maybe when you need to know why something failed.
data Either a b = Left a | Right b
parseAge :: String -> Either String Int
parseAge s = case readMaybe s of
Nothing -> Left "not a number"
Just n -> Right n
Decompose data by its structure. Every algebraic data type can be taken apart as precisely as it was put together.
describeList :: [a] -> String
describeList xs = case xs of
[] -> "empty"
[_] -> "singleton"
_ -> "longer list"
The abstraction for sequencing computations that produce effects. Bind (>>=) takes a value in context and a function that produces a new context, composing them cleanly.
class Applicative m => Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
A type that can be mapped over. The fmap function applies a function to the value inside a context without altering the context itself.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Between Functor and Monad. Apply functions that are themselves wrapped in context to values in context.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Syntactic sugar that makes monadic code read like imperative programming, while preserving all the guarantees of the type system.
main :: IO ()
main = do
name <- getLine
putStrLn ("Hello, " ++ name)
Ad-hoc polymorphism. A type class defines an interface; instances provide implementations for specific types. Constraints propagate through the type system.
class Eq a where
(==) :: a -> a -> Bool
Types that take type constructors as arguments. This enables abstractions over containers themselves, not just their contents.
-- f is a type constructor (* -> *)
class Functor (f :: * -> *) where ...
safeDivide :: Int -> Int -> Maybe Int
safeDivide _ 0 = Nothing
safeDivide x y = Just (x `div` y)
main :: IO ()
main = do
let result = safeDivide 10 3
case result of
Nothing -> putStrLn "Cannot divide by zero"
Just n -> putStrLn ("Result: " ++ show n)
The quest continues.