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
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
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
.
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
You might enjoy the talk “Point-Free or Die: Tacit Programming in Haskell and Beyond” from Strange Loop 2016.
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))
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]