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
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
Flip the order of first two arguments to a function.
flip :: (a -> b -> c) -> b -> a -> c
flip f y x = f x y
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 function application, along with its r
ight-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
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.
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
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
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))