Functions

Recall the following from before:

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

This definition, and others like it, define a helper function to be used just once. There are several ways to avoid this: with lambdas, by flipping the order of function arguments to expose an opportunity for partial application, 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 + partial application
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

Patterns

Actually, can write arbitrary patterns (allowed for the particular input type):

\p -> e

But, just like with let p = e, there is a run-time error if e evaluates to a value that does not match p.

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

You might enjoy the talk “Point-Free or Die: Tacit Programming in Haskell and Beyond” from Strange Loop 2016.

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))

More examples:

sum  = foldr (+) 0
prod = foldr (*) 1
cat  = foldr (++) []

len = foldr (\_ acc -> 1 + acc) 0
len = foldr (\_ -> (1+)) 0         -- not necessarily better
len = foldr (+) 0 . map (const 1)  -- same here, and two passes

Exercise: Implement indexedMap :: (Int -> a -> b) -> [a] -> [b]