Skip to content

Instantly share code, notes, and snippets.

@vpayno
Last active December 23, 2020 03:54
Show Gist options
  • Save vpayno/2286ebf81b39f18f38c5 to your computer and use it in GitHub Desktop.
Save vpayno/2286ebf81b39f18f38c5 to your computer and use it in GitHub Desktop.
Haskell Cheat Sheets - Functions

By Victor Payno

Functions

Rules

  1. Start with a lowercase letter.

  2. Functions are immutable.

  3. The apostrophe (') is valid part of a function name.

    • Used at the end to represent a strict version (not lazy) of a function or a slightly modified version of a function.
  4. When given the same inputs the function always does/returns the same thing.

  5. Functions are daisy-chained together to manipulate data.

  6. Always returns a value.

  7. Don't use parenthesis around the function paramters.

  8. Do use parenthesis to indicate precedence.

  9. You can use backtics to convert a prefix function into an infix function.

  10. Functions are lazy because they don't do work unless they have to.

4 == div 8 2

4 == 8 `div` 2

infix Functions

The syntax for infix functions is: param1 function param2

The -, +, /, *, ^, ^^, and ** symbols are functions!

(-3) == 3 * (-1)

prefix Functions

Prefix functions that act on numbers.

  • succ #

  • min # #

  • max # #

  • div # #

  • abs #

  • mod # #

  • odd #

  • even #

To define a prefix function, name it, list the parameters, and set it equal to an expression.

power n e = n ** e

Laziness in Functions

A function is said to be lazy when it only does the bare minimum work assigned to it.

For instance, the null function returns false immediately after it sees that the list it's passed has a single value. There's no point in waiting around to see if it has more elements. On the other hand, it has to wait until we're done if it never sees any elements in the list. This makes more sense when using infinite sets in list comprehensions.

To prove that null is lazy all you have to do is pass it an infinite set and see that it returns immediately.

null [1..]
> False

If null wasn't lazy it would wait until it received the end of the list which never happens.

A drawback of having lazy functions is that Haskell only keeps track of the beginning of a list and must traverse every element in the list to find last element. This is why it's faster to prepend elements to a list using the cons operator instead of appending them.

Pattern Matching in Functions

You can define multiple function bodies with unique function signatures. They are a way of making sure a value conforms to a form or pattern.

The parameter patterns are matched from top to bottom. You should always define a catch-all pattern or you'll get the "Non-exhaustive patterns in function..." error.

magic :: (Integral a) => a -> String
magic 1 = "one"
magic 2 = "two"
magic n = "other"

magic 1
> "one"

magic 2
> "two"

magic 3
> "other"

We can also do this with tuples.

  • Without pattern matching:
vectorAdd :: (Num a) => (a, a) -> (a, a) -> (a, a)
vectorAdd :: a b = (fst a + fst b, snd a + snd b)
  • With pattern matching:
vectorAdd :: (Num a) => (a, a) -> (a, a) -> (a, a)
vectorAdd :: (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)

We can also use lists in pattern matching.

For instance, to use it to define a variable argument list.

addNumbers :: (Num a) => [a] -> a
addNumbers (x:y:z:zs) = x + y + z + sum zs

addNumbers [1, 1, 1, 1]
> 4

addNumbers [1, 1, 1, 1, 1]
> 5

addNumbers [1, 1, 1]
> 3

addNumbers [1, 1]
> *** Exception: patterns.hs:7:1-42: Non-exhaustive patterns in function addNumbers

You can also just discard the elements that you don't want. Patterns with ":" must match against a list with one or more elements.

tell :: (Show a) => [a] -> String
tell [] = "Empty list"
tell (x:[]) = "One Element: " ++ show x
tell (x:y:[]) = "Two Elements: " ++ show x ++ " and " ++ show y
tell (x:y:_) = "Two or More Elements: " ++ show x ++ " and " ++ show y

tell [1]
> "One Element: 1"

tell [1, 2.2]
> "Two Elements: 1.0 and 2.2"

tell [1, 2.2, 3]
> "Two or More Elements: 1.0 and 2.2"

tell [1..]
> "Two or More Elements: 1 and 2"

We can also use pattern matching to implement recursion.

length' :: (Num b) => [a] -> b  
length' [] = 0  
length' (_:xs) = 1 + length' xs
  • To calculate the factorial of n without pattern matching and recursion:
factorial :: (Integral a) => a -> a
factorial n = product [1..n]

factorial 10
> 3628800
  • To calculate the factorial of n with pattern matching and recursion:
factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)

factorial 10
> 3628800

We can also throw away values with pattern matching and just use the ones we need.

  • Let's reimpliment the fst and snd functions and add a third one.
fst' :: (a, b, c) -> a
fst' (x, _, _) = x

fst' (1,2,3)
> 1

snd' :: (a, b, c) -> b
snd' (_, y, _) = y

snd' (1,2,3)
> 2

trd' :: (a, b, c) -> c
trd' (_, _, z) = z

trd' (1,2,3)
> 3

Pattern matching also works with list comprehension.

'''
xs = [(1,2), (3,4), (4,5)]
[ a+b | (a,b) <- xs]
> [3,7,9]

As Patterns in Functions

Same as a pattern except it gets a name.

For instance with all@(x:y:xs) you can use the all name instead of x:y:xs inside the function.

parts :: String => String
parts all@(x:xs) = "The first element of " ++ all ++ " is " ++ [x]

parts "abc"
> "The first element of abc is a"

Guards in Functions

Guards test to see if a property (or properties) of a value are true or false.

Allow us to bind variables and functions to the end of a function with a scope local to that function.

The otherwise catch-all guard evaluates to True.

  • Let's reimplement the min and *max" functions. In this example we need to use Ord instead of Num because we want to compare the numbers.
max' :: (Ord a) => a -> a -> a
max' a b
    | a > b     = a
    | otherwise = b

min' :: (Ord a) => a -> a -> a
min' a b
    | a < b     = a
    | otherwise = b

max' 2 4
> 4

min' 2 4
> 2

max' 3 3
> 3
  • Let's build a comparator function.
comp :: (Ord a) => a -> a -> Ordering
comp a b
    | a > b     = GT
    | a == b    = EQ
    | otherwise = LT 

comp 3 5
> LT

3 `comp` 5
> LT

3 `comp` 1
> GT

3 `comp` 3
> EQ

comp could have also been defined as an infix function.

...
a `comp` b
...

The where clause can be used to store a value/equation used by the guard tests.

rectangle :: (RealFloat a) => a -> a -> String
rectangle height width
    | area <= 10.0 = "That's a small rectangle."
    | area <= 20.0 = "That's a medium rectangle."
    | area <= 30.0 = "That's a large rectangle."
    | otherwise    = "That's a huge rectangle."
    where area = height * width

rectangle 1 2
> "That's a small rectangle."

rectangle 3 4
> "That's a medium rectangle."

rectangle 5 6
> "That's a large rectangle."

rectangle 7 8
> "That's a huge rectangle."

We can also use additional where bindings to define additional variables (only available to the defining function). Here white space is important, otherwise Haskell won't know they are part of the function's where clause.

rectangle :: (RealFloat a) => a -> a -> String
rectangle height width
    | area <= small  = "That's a small rectangle."
    | area <= medium = "That's a medium rectangle."
    | area <= large  = "That's a large rectangle."
    | otherwise      = "That's a huge rectangle."
    where area   = height * width
          small  = 10.0
          medium = 20.0
          large  = 30.0

Where bindings can also be defined using pattern matching.

  • Here's an example of using a tuple.
rectangle :: (RealFloat a) => a -> a -> String
rectangle height width
    | area <= small  = "That's a small rectangle."
    | area <= medium = "That's a medium rectangle."
    | area <= large  = "That's a large rectangle."
    | otherwise      = "That's a huge rectangle."
    where area   = height * width
          (small, medium, large) = (10.0, 20.0, 30.0)
  • Here's an example of using lists and discarding values.
initials :: String -> String -> String
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."
    where (f:_) = firstname
          (l:_) = lastname

initials "John" "Doe"
> "J. D."

We can also define and use functions in the where clause.

  • In this example we use a function with list comprehension since we have to calculate the area for each pair.
calcAreas :: (RealFloat a) => [(a, a)] -> [a]
calcAreas xs = [area height width | (height, width) <- xs]
    where area height width = height * width

calcAreas [(1,2), (3,4), (5,6)]
> [2.0,12.0,30.0]

Let Bindings in Functions

Let bindings let you add variables to a function but are only local to its Guard. They cannot be used across Guards like where bindings.

Let bindings are expressions where where bindings are syntactic constructs.

The form is Let in .

cylinder :: (RealFloat a) => a -> a -> a  
cylinder r h = 
    let sideArea = 2 * pi * r * h  
        topArea = pi * r ^2  
    in  sideArea + 2 * topArea  

cylinder 3 10
> 245.04422698000386

Since let bindings are expressions they can be used anywhere expressions can be used.

3 + (if True then 4 else 8) + 5
> 12

3 + (let x = 7 in x * 2) + 3
> 20

They can also add a function to a local scope.

[let double x = x * 2 in (double 4, double 12)]
> [(8,24)]

In the last expression we can't use white space to align several bindings so we use semi-colons instead.

[let double x = x * 2 ; tripple x = x * 3 in (double 4, tripple 12)]
> [(8,36)]

let bindings can also be used inside list comprehension. It's a little backwards. The in keyword was left out because the lisst comprehension already defines its scope.

calcVolumes :: (Num a) => [(a, a, a)] -> [a]
calcVolumes xs = [volume | (h, w, d) <- xs, let volume = h * w * d]

calcVolumes [(1,2,3), (4,5,6)]
> [6,120]

Since the last example doesn't filter the list, we can add a predicate to do that.

calcVolumes :: (RealFloat a) => [(a, a, a)] -> [a]
calcVolumes xs = [volume | (h, w, d) <- xs, let volume = h * w * d, volume > 100]

calcVolumes [(1,2,3), (4,5,6)]
> [120.0]

Let expressions are used in the interactive shell to define variables and functions. When the in keyword is used the expression is only available to the body of the in scope.

*Main> let foo x y = x + y
*Main> foo 1 2
3
*Main> let foo x y = x + y in foo 3 4
7
*Main> foo 5 6
11
*Main> let foo x y = x + y
*Main> foo 1 2
3
*Main> let goo x y = x * y in goo 3 4
12
*Main> goo 5 6

<interactive>:381:1:
    Not in scope: `goo'
    Perhaps you meant `foo' (line 378)

Case Expressions

Case Expressions are the same thing as pattern matching on parameters in function definitions. They can be used anywhere an expression can be used.

The expression is matched against the patterns and the respective result is returned.

  • These two expressions are equivalent.
head' :: [a] -> a
head' [] = error "List is empty."
head' (x:_) = x

head'' :: [a] -> a
head'' xs = case xs of []    -> error "List is empty."
                       (x:_) -> x

head' [1,2,3]
> 1

head'' [1,2,3]
> 1
  • Here is another example.
describeList :: [a] -> String
describeList xs = "The list is " ++ case xs of [] -> "empty."
                                               [x] -> "a singleton list."
                                               xs -> "a longer list."

describeList' :: [a] -> String
describeList' xs = "The list is " ++ describe xs
    where describe [] = "empty."
          describe [x] = "a singleton list."
          describe xs = "a longer list."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment