Functions

Recall the following from before:

doubleList xs = map ((*) 2) xs

squareList xs = map square xs
  where square x = x ^ 2

The latter defines a helper definition to be used just once. There are several ways to avoid this: with lambdas, by flipping the order of function arguments, and with sections. No matter which version we choose, we can further simplify the definition via eta-reduction.

squareList xs = map (\x -> x ^ 2) xs   -- lambda
squareList xs = map (flip (^) 2) xs    -- flip
squareList xs = map (^2) xs            -- section
squareList    = map (^2)               -- eta-reduction

Lambdas

All function definitions we have seen so far are syntactic sugar.

addOne x = 1 + x  ===  addOne = \x -> 1 + x

add x y  = x + y
  ===  add = \x -> \y -> x + y    -- nested lambda
  ===  add = \x y -> x + y        -- sugar for nested lambdas

Arrows associate to the right (in expressions and in types)

 S ->  (T ->  U)

\x -> (\y ->  e)

 x :: S
 y :: T
 e :: U

So, "multi-argument" functions:

e1 e2 e3 e4 e5  ===  ((((e1 e2) e3) e4) e5)

e1 :: T2 -> (T3 -> (T4 -> T5))
e2 :: T2
e3 :: T3
e4 :: T4
e5 :: T5

Function Application and Composition

Flipping Arguments

Flip the order of first two arguments to a function.

flip :: (a -> b -> c) -> b -> a -> c
flip f y x = f x y

Currying / Uncurrying

curry :: ((a, b) -> c) -> a -> b -> c
curry f a b = f (a, b)

uncurry :: (a -> b -> c) -> (a, b) -> c
uncurry f (a, b) = f a b

Infix Application

Infix function application, along with its right-associative fixity declaration, allows nested function calls to be right-associated without writing so many parentheses. Don't worry too much about precedence levels (here 0) for infix operators.

infixr 0 $

($) :: (a -> b) -> a -> b
($) f x = f x

Note that infix operators can be defined in infix style:

f $ n = f x

Left- vs. right-associated nested function calls:

        e1 e2 e3 e4 e5  ===  ((((e1 e2) e3) e4) e5)

(e1 (e2 (e3 (e4 e5))))  ===  e1 $ e2 $ e3 $ e4 e5

Pipeline Application

The $ operator can improve the clarity of a "backward pipeline" of function calls. The expression

double $ square $ double $ 3

can be read "pipe" 3 into the double function, then pipe its result to the square function, then pipe its result to the double function. Choosing (<|) to denote this operator, rather than ($), may make the backward pipelining more visually apparent:

double <| square <| double <| 3

We can also define a "forward pipeline" operator:

(|>) :: a -> (a -> b) -> b
x |> f = f x

For example:

3 |> double |> square |> double

Or, when each of the functions gets too long to fit on a line:

x |> fReallyReallyReallyLongExpression
  |> gReallyReallyReallyLongExpression
  |> hReallyReallyReallyLongExpression

Using operators to (<|) and (|>) denote backward and forward pipelining is common in other functional languages like OCaml and Elm. In Haskell, however, the operators ($) and (&) (defined in Data.Function) are the norm.

Composition

compose :: (b -> c) -> (a -> b) -> a -> c
compose f g x = f (g x)

Infix operator composition.

(.) f g x = f (g x)

Alternate definition.

(f . g) x = f (g x)

Point-free notation. A "point" is an explicit, named argument, not the (.) operator.

squareAndDouble x = compose double square x
squareAndDouble   = compose double square
squareAndDouble   = double . square

Sections

A section is the partial application of a binary operator to one of its arguments.

(n op) === (op) n === (\m -> n op m)

(op m) === flip (op) m === (\n -> n op m)

> (+3) 4
> let plus3 = (+3)      -- let plus3 x = x + 3
> (3+) 4
> let plus3 = (3+)      -- let plus3 x = 3 + x
> (*1) == (1*)
> (1-) 10
> (-1) 10               -- special case: unary minus!
> (subtract 1) 10       -- poor man's "section"
> (^) 2 3
> (^) 2
> flip (^) 3

Speaking of binary operators, we have seen how infix operators can be used as prefix functions.

> (+) 1 2

Prefix functions can also be used as infix operators.

> let plus = (+)
> 1 `plus` 2

Eta-Reduction

f x = g x
f   = g

f x = (BIG_COMPLICATED_EXPRESSION) x
f   = (BIG_COMPLICATED_EXPRESSION)

Note that this eta-reduction pattern is only valid if x is not referred to in the BIG_COMPLICATED_EXPRESSION.

For example:

double x = (2*) x
double   = (2*)

square x = (^2) x
square   = (^2)

squareAndDouble x = compose (*2) (^2) x
squareAndDouble   = compose (*2) (^2)
squareAndDouble   = (*2) . (^2)

squareAndDoubleList xs = map squareAndDouble xs
squareAndDoubleList xs = map ((*2) . (^2)) xs
squareAndDoubleList    = map ((*2) . (^2))