Haskell Cheatsheet

Typeclasses

Use this Haskell reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Defining a Typeclass

class Describable a where
  describe :: a -> String

  shortName :: a -> String
  shortName = take 8 . describe   -- default method

A typeclass is an interface for types. Methods may ship default implementations.

Instances

data User = User { userName :: String }

instance Describable User where
  describe user = "User: " ++ userName user

Instances attach behavior to a concrete type.

Deriving Common Instances

data Status = Draft | Published | Archived
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

data Box a = Box a
  deriving (Show, Functor, Foldable, Traversable)

The Functor/Foldable/Traversable/Generic derivations are on by default under GHC2021.

Semigroup and Monoid

Semigroup is an associative combine (<>). Monoid adds an identity element (mempty).

[1,2] <> [3]              -- [1,2,3]
"foo" <> "bar"            -- "foobar"
Just [1] <> Just [2]      -- Just [1,2]
mempty :: String          -- ""
mconcat ["a","b","c"]     -- "abc"
foldMap show [1,2,3]      -- "123", map then <> everything

Writing your own:

newtype MaxInt = MaxInt Int deriving (Eq, Show)

instance Semigroup MaxInt where
  MaxInt a <> MaxInt b = MaxInt (max a b)

instance Monoid MaxInt where
  mempty = MaxInt minBound

Functor

fmap (+1) (Just 2)         -- Just 3
(+1) <$> Just 2            -- same thing, <$> is infix fmap
fmap length ["a", "abc"]   -- [1,3]
fmap (*2) (Right 5)        -- Right 10, maps the Right side only

Functor means you can map a pure function over a value in a context.

Applicative

pure (+) <*> Just 2 <*> Just 3   -- Just 5
(+) <$> Just 2 <*> Just 3        -- idiomatic applicative style
liftA2 (,) (Just 1) (Just 2)     -- Just (1,2)
[(+1), (*2)] <*> [10,20]         -- [11,21,20,40]

Applicative applies functions that are themselves inside a context. One Nothing anywhere makes the whole result Nothing.

Monad

Monad sequences computations where the next step depends on the previous result. >>= (bind) feeds a wrapped value into the next function, short-circuiting on failure:

half :: Int -> Maybe Int
half n = if even n then Just (n `div` 2) else Nothing

Just 20 >>= half >>= half            -- Just 5
Just 20 >>= half >>= half >>= half   -- Nothing (5 is odd)

Do Notation Desugars to >>=

quarter :: Int -> Maybe Int
quarter n = do
  h <- half n
  q <- half h
  pure q

-- exactly equivalent to:
quarter' :: Int -> Maybe Int
quarter' n = half n >>= \h -> half h >>= \q -> pure q

do works for every monad, not just IO. Each x <- action becomes a >>=, a bare action line becomes >>, and the last line is the result.

Writing a Monad Instance

Every Monad must also be a Functor and an Applicative:

newtype Identity a = Identity { runIdentity :: a }

instance Functor Identity where
  fmap f (Identity a) = Identity (f a)

instance Applicative Identity where
  pure = Identity
  Identity f <*> Identity a = Identity (f a)

instance Monad Identity where
  Identity a >>= f = f a

Foldable and Traversable

Foldable reduces a structure (sum, length, elem, toList, foldr all work on any Foldable). Traversable maps an effectful function and collects the results:

import Text.Read (readMaybe)

traverse readMaybe ["1","2","3"] :: Maybe [Int]  -- Just [1,2,3]
traverse readMaybe ["1","x","3"] :: Maybe [Int]  -- Nothing

sequenceA [Just 1, Just 2]   -- Just [1,2], flips [f a] into f [a]
sequenceA [Just 1, Nothing]  -- Nothing

mapM print [1,2,3]           -- traverse specialized to Monad
mapM_ print [1,2,3]          -- same, discarding results
traverse_ print [1,2,3]      -- Foldable version (Data.Foldable)

Mnemonic: traverse = map + sequenceA. One failed element fails the whole traversal.