03 Functions
Functions
The most important building blocks in functional programming are functions. Function application binds strongest in Haskell and associates to the left.
f a + b == (f a) + b
g a b == ((g a) b)
Function Definition
A definition associates a name with a value of a particular type.
- The name is used to associate the value with the type and later refer to the function.
- The type is describes how to interact with the function (what are its inputs and outputs).
- The expression defines what the function does. There might be multiple expressions with the same name.
name :: type
name = expression
Pattern Matching
The left-hand side of a function definition is called the pattern. It determines which function’s computation rules are applied.
Patterns are matched in order from top to bottom. Place most specialized pattern first. Then follow with more general patterns.
Patterns may include:
- Constant values like
0or[] - Names like
n - Wildcards like
_ - Structures or tuples like
(x:xs)or(a,b) - and more…
Function Expressions
There are multiple ways to define functions in Haskell.
Partial Application
Functions in Haskell are curried by default. This means that functions with multiple parameters are actually a series of functions, each taking a single parameter.
For example: add :: Int -> Int -> Int is actually add :: Int -> (Int -> Int).
Meaning add is a function that takes an Int and returns a function (Int -> Int) which in turn takes an Int and returns an Int.
This also means that the function arrow -> is right-associative.
Currying and Uncurrying
Currying: the process of transforming a function that takes multiple arguments into a series function, each taking a single argument. Uncurrying: the reverse process of currying.
Lambda Expressions
Lambda expressions are anonymous functions (functions wihtout a name) that are used because we need some functions only once.
-- General form: \p -> e
\x -> x + 1 -- Increment by one
-- With multiple parameters:
\x y -> x + y -- Add two numbers
\x -> \y -> x + y -- Same as above, but with explicit currying
Higher Order Functions
Functions that take functions as parameters or return functions as return values are called higher order functions. Functions are not special in Haskell, they are just values like everything else! This is why currying works (functions that return functions) and why functions can be passed as parameters.
-- Example of a higher order function:
filter :: (a -> Bool) -> [a] -> [a] -- first parameter is a function from a to Bool
onlyPositive = filter (\x -> x >= 0) -- partial application: onlyPositive :: [Int] -> [Int]
onlyPositive [1, -2, 3, -4] -- evaluates to [1, 3]
Function Composition
Functions can be combined into “pipelines” if the types line up.
This is called function composition and is denoted by a dot ((.)).
f :: a -> b
g :: b -> c
g.f :: a -> c -- g(f(x)): apply f then g
-- Example
map (\x -> negate (sum (tail x))) [[1,2,3],[4,5,6],[7,8]]
map (negate . sum . tail) [[1,2,3],[4,5,6],[7,8]] -- same as above
Operators
While function names start with a lower case letter, operators are formed from one or more symbol characters.
Operators are written between (infix) their arguments (e.g. 3+4).
Operators can also be used as functions by enclosing them in parentheses (e.g. (+) 3 4).
And functions can be used as operators by enclosing them in backticks (e.g. 9 `div` 2).
(+) :: Num a => a -> a -> a
(+) 3 4 -- evaluates to 7
div :: Integral a => a -> a -> a
9 `div` 2 -- evaluates to 4
Sections
When infix operators are partially applied, they become sections.
When they are applied to no arguments, Haskell treats them as equivalent functional values: (+) = \x y -> x + y.
Precedence
Operators have a precedence, which determines how tightly they bind to their arguments.
The precedence of an operator can be defined by the fixity (infix, infixl, infixr) declaration.
| Op | Assoz | Präz |
|---|---|---|
|| (logical or) | r | 2 |
&& (logical and) | r | 3 |
==, /=, <, >, <=, >= | - | 4 |
++, : | r | 5 |
+, - | l | 6 |
*, / | l | 7 |
^ | r | 8 |
. | r | 9 |
() (Function application) | l | 10 |
Custom Operator
-- Define a custom operator with precedence 6 and left associativity:
infixl 6 *+*
(*+*) :: Int -> Int -> Int
a *+* b = a ^ 2 + b ^ 2
-- left associative means that the following are equivalent:
1 *+* 2 *+* 3 *+* 4
((1 *+* 2) *+* 3) *+* 4