Hugonweb | Haskell Tips

Cheatsheet

$ Use in place of parenthesis for function calls/applicatives/etc. a (b c) = a $ b c
. Function composition, useful in the "point free style" with no arguments f = g . h
liftIO When you have something that returns an IO monad f, and you want to put it where some other (but containing IO) monad should be, then you can do liftIO f. It converts an IO monad into the required monad if compatible.
<$> Shorthand for fmap; maps a function taking plain old types onto data wrapped in a functor, applicative, or monad, returning wrapped contents
<*> Similar to <$>, but also the function is wrapped in an applicative, just like the arguments and result
sequenceA flips around applicatives e.g. turns a list of Maybe into Just a list or Nothing
traverse Applicative f => (a -> f b) -> t a -> f (t b) maps a function reurning an applicative and then flips the applicative outside of the e.g. list
[1..5] = [1,2,3,4,5]
[1,3..9] = [1,3,6,9] note lists can only have even spacing between values
[1..] = [1,2,3,4,5, and so on forever]
[ expr | var <- list, predicate ] List comprehension e.g. [ 2*x | x <- [0..4], x > 3] = [6,8]

With applicatives, you can do

> (*) <$> Just 3 <*> Just 4
Just 12

and just add another <*> (Just 5) if the initial function takes 3 arguments, and so on.

Examples with lists

> let x = [1..3]
> let y = Just <$> x
> y
[Just 1,Just 2,Just 3]
> let z = Nothing : y
> z
[Nothing,Just 1,Just 2,Just 3]
> sequenceA y
Just [1,2,3]
> sequenceA z
Nothing
> traverse Just x
Just [1,2,3]

Applicatives and Monads

After Haskell made Monad a subclass of Applicative, you can always use the new (applicative or functor) name for all of these:

All copied from https://entropicthoughts.com/haskell-procedural-programming#things-you-never-need-to-care-about by Christoffer Stjernlöf

On the other hand, using these most general functions can make error messages more confusing when using lists. Concatenation with ++ instead of <> (from Semigroup) and mapping with map instead of fmap or <$> (from Functor) can be more readable and simpler when working with a list of monads/applicatives/etc. But beware that ++ doesn't work with Text, <> must be used!

Do Notation

In the Monad do notation,

do 
    a <- as
    bs a

Translates to as >>= bs.

Modules

If you defined a data type, then this only exports the type constructor:

module XXX
    (
        MyDatatype
    )

This exports the data constructor (and accessor functions for a record) as well:

module XXX
    (
        MyDatatype (..)
    )

Records

The following GHC extensions make it so you don't have to worry about record field names colliding with other record field names or general variables and functions.

The type of record you are constructing/updating can even be inferred if the set of fields you use to construct/update the record is unique to the type.

{-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# LANGUAGE OverloadedRecordDot #-}

data Foo = MkFoo { x :: Int, y :: Int }
data Bar = MkBar { x :: Int, z :: Int }

-- Constructing records:
x = MkFoo { x = 5, y = 7 }
y = MkBar { x = 0, z = 3 }

-- Updating records:
baz = x { y = 6 } -- inferred to be of type Foo

main :: IO ()
  -- Accessing record fields
  print baz.x -- 5
  print baz.y -- 6
  print y.z   -- 3
  -- This method of access is disabled to not have to worry about name
  -- collisions:
  print $ z y -- error

The following GHC extensions let you type less when pattern matching on records:

{-# LANGUAGE NamedFieldPuns #-} -- Available by default in GHC2021 and GHC2024
{-# LANGUAGE RecordWildCards #-}

data Fizz = MkFizz {alpha :: Int, beta :: Int, gamma :: Int, delta :: Int}

myfizz = MkFizz {alpha = 10, beta = 2, gamma = 3, delta = 5}

-- Field puns make it easy to extract a field by name from a record in a
-- pattern match
ten (MkFizz {alpha}) = alpha

-- Without named field puns, you have to do this:
five (MkFizz {delta = delta}) = delta

-- Record wildcards mean you match any and all used fields without having to
-- type them
twenty (MkFizz {..}) = alpha `div` beta + gamma * delta

main :: IO ()
main = do
  print $ ten myfizz -- 10
  print $ five myfizz -- 5
  print $ twenty myfizz -- 20