← Fundamentals
Guided lesson 7 questions · Haskell only

Currying and partial application

A function of two arguments is a function that returns a function. Give it one argument and look at what remains.

Every Haskell function takes one argument. Int -> Int -> Int means Int -> (Int -> Int): applying such a function to one argument gives a function that waits for the next one, so the order of the parameters decides which partial applications you can write.

Written for Haskell.

Before you begin

Why this matters

Most Haskell code passes partial applications and operator sections to map, filter, and other functions. If you read Int -> Int -> Int as a chain of one-argument functions, you can predict what a partial application or a section computes, and read GHC’s errors when an application has too few or too many arguments.

What this helps you reason about

  • Say what tourPrice 2 is, and what its type is.
  • Predict which parameter a partial application or a section fills, and spot (- 1) used as a function.
  • Choose a parameter order that suits partial application, and convert between two arguments and a pair.

In JavaScript, Python, or Java, a function of two parameters is called with both arguments at once, and a call with one argument is a mistake or a missing value. Function calls showed that Haskell is different: area 3 is a function that waits for the height. This lesson starts from the type. It shows what Int -> Int -> Int says about application, what is left after one argument, why the order of the parameters matters, and how operators, pairs, and point-free definitions fit the same rule.

Already know this? The short version
  • Every Haskell function takes one argument: Int -> Int -> Int means Int -> (Int -> Int), and tourPrice 2 3 means (tourPrice 2) 3, so tourPrice 2 is a function of type Int -> Int that keeps the 2.
  • A partial application fills the first parameter, so put the argument that changes least first; flip f x y is f y x, for a use that needs the other order.
  • A section fills one side of an operator: (/ 2) x is x / 2, and (2 /) x is 2 / x; (- 1) is a number, so write subtract 1. uncurry f (a, b) is f a b, and curry goes the other way.
  • Too few arguments where a value is needed gives No instance for (Show (Int -> Int)); too many gives Couldn't match expected type ‘t0 -> a0’ with actual type ‘Int’. The section “What goes wrong with partial application in real programs?” covers sections the wrong way round, (- 1) used as a function, a stored partial application with its arguments in the wrong order, and point-free code nobody can read.

Go to the final check· Go to the practice tickets

Words in this lesson (6)
function type
A type written with arrows, such as Int -> Int for a function from Int to Int. The arrow groups to the right, so Int -> Int -> Int means Int -> (Int -> Int).
currying
Writing a function of several arguments as a chain of functions that each take one argument. Every Haskell function is curried: a function of type Int -> Int -> Int takes an Int and returns an Int -> Int.
partial application
Applying a function to fewer arguments than it takes, such as area 3. The result is a function that waits for the remaining arguments.
operator section
An operator with one side filled in, written in parentheses, such as (+ 10) or (>= 100). It is a function that waits for the missing side.
tuple
A value that groups a fixed number of values, written in parentheses, such as (30, 12). fst and snd give the parts of a pair.
point-free style
Defining a function without naming its last argument, such as halveAll = map (/ 2) instead of halveAll fees = map (/ 2) fees.

Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.

01 / One argument

What does Int -> Int -> Int say about application?

In Haskell, every function takes exactly one argument. tourPrice :: Int -> Int -> Int looks like a function of two arguments. But the arrow groups to the right, so the type means Int -> (Int -> Int): tourPrice takes the hours and returns a function, and that function takes the number of people and returns the price. Writing a function of several arguments as a chain of one-argument functions is called currying.

Application groups to the left, so tourPrice 2 3 means (tourPrice 2) 3. That is why tourPrice 2 on its own is a complete expression. It is a partial application: a value of type Int -> Int that keeps the 2 and waits for the people. You can name it, pass it to another function, and apply it later, as often as you need.

GHC checks how many arguments you give. With one missing where a printable value is needed, as in print twoHours, GHC reports No instance for (Show (Int -> Int)) arising from a use of ‘print’, and adds (maybe you haven't applied a function to enough arguments?). print cannot show a function. With one argument too many, as in tourPrice 2 5 1, GHC reports Couldn't match expected type ‘t0 -> a0’ with actual type ‘Int’, and then: The function ‘tourPrice’ is applied to three value arguments, but its type ‘Int -> Int -> Int’ has only two.

Main.hs
tourPrice :: Int -> Int -> Int
tourPrice hours people = hours * 6 * people + 4

-- tourPrice 2 is a function of the people
twoHours :: Int -> Int
twoHours = tourPrice 2
-- twoHours 3 is 40, the same as tourPrice 2 3

Every Haskell function takes one argument; a function of two arguments returns a function, and applying it to one argument gives that function.

Coming from JavaScript: tourPrice(2) in JavaScript runs the function with people undefined and gives NaN; tourPrice 2 in Haskell runs nothing and gives a function.

Coming from Java: A two-parameter method cannot be called with one argument. In Haskell that application is allowed, and its type, Int -> Int, says what it gives.

Precisely: what GHC builds

The Haskell 2010 Report defines application one argument at a time. GHC’s compiled code does not make a new function for each argument: when a function receives all the parameters of its equation at once, GHC calls it directly, and it builds a partial-application value only when arguments are missing. The result is the same either way; only the speed differs.

Experiment

Give a function one argument, then the next

tourPrice 2 is a function that keeps hours = 2 and waits for people. Line 7 applies it to 3, and only then does the equation of tourPrice run, with both values. twoHours is a binding, so it is computed once and shared by both applications.

Why it matters A partial application is a value: name it once and apply it as often as you need. When GHC cannot show a value of a function type, an argument is missing.

twoHours is tourPrice with only the hours given. Predict the two printed prices, and how many times GHCi stops on line 6, where twoHours is defined. Then change line 8 to print twoHours and predict what happens.

tourPrice :: Int -> Int -> Int
tourPrice hours people = hours * 6 * people + 4

main :: IO ()
main = do
  let twoHours = tourPrice 2
  print (twoHours 3)
  print (twoHours 5)
What to look for

GHCi shows one location per stop, not a stack of frames, and a value that is not computed yet as _ (unevaluated). After line 5, GHCi stops twice on line 7, then on line 6, Main.main.twoHours, where tourPrice 2 is computed. Then it stops three times in tourPrice on line 2: twice with hours = 2 and people = 3, then with hours = 2 alone, while it computes hours * 6. The 2 came from line 6, and the 3 from line 7. The run prints 40. For twoHours 5 there is no stop on line 6, because twoHours was already computed. The stops on line 2 show hours = 2 and people = 5, and the run prints 64. twoHours shows _ (unevaluated) at every stop, even after it was computed, because GHCi cannot display a function. On a computer, rest the pointer on the name twoHours to see its type, Int -> Int. After the edit, GHCi refuses to load the program: No instance for (Show (Int -> Int)) arising from a use of ‘print’, followed by (maybe you haven't applied a function to enough arguments?).

Expected output

40
64

02 / Argument order

Which argument should come first?

A partial application always fills the first parameter. So the order of the parameters decides which partial applications you can write. shareOf people total = total `div` people shares a tour’s total among the people; `div` divides whole numbers and drops the remainder. shareOf 4 means “a quarter of any total”, which suits map (shareOf 4) [120, 200].

To share one total of 120 among groups of 2 and of 3, shareOf 120 is wrong: it makes 120 the people. Both parameters are Int, so GHC accepts it, and map (shareOf 120) [2, 3] gives [0,0].

A common rule is to put the argument that changes least first. A rate, a fee, or a setting usually comes first, and the value that changes at each use, such as each price in a list, comes last. The Prelude follows this rule: map and filter take the function first and the list last.

When one use needs the other order, flip swaps the first two arguments: flip f x y is f y x. flip shareOf 120 is a function of the people with 120 as the total. A lambda that names the parameter says the same thing: \people -> shareOf people 120.

Main.hs
shareOf :: Int -> Int -> Int
shareOf people total = total `div` people

-- one group, several tours: the order fits
quarters = map (shareOf 4) [120, 200]
-- [30,50]
-- one tour, several groups: use flip
perGroup = map (flip shareOf 120) [2, 3]
-- [60,40]

A partial application fills the first parameter; put the argument that changes least first, and use flip or a lambda when one use needs the other order.

Coming from Python: functools.partial can fill any parameter by its name. A Haskell partial application fills the first parameter, so the order in the definition matters.

Experiment

Fill the wrong parameter with a partial application

A partial application fills the parameters from the left. shareOf 120 makes 120 the people, whatever the 120 was meant to be. flip shareOf 120 gives 120 to the second parameter, total. Both parameters are Int, so GHC cannot see the mistake.

Why it matters Before you partially apply a function, read the order of its parameters. When the parameters have the same type, only the result shows the mistake.

shareOf takes the people first and the total second. Predict both printed lists. Then change shareOf 120 on line 7 to flip shareOf 120 and predict the second list again.

shareOf :: Int -> Int -> Int
shareOf people total = total `div` people

main :: IO ()
main = do
  print (map (shareOf 4) [120, 200])
  print (map (shareOf 120) [2, 3])
What to look for

For the first list, the stops on line 2 show people = 4 with total = 120, then with total = 200, and the run prints [30,50]. For the second list, the stops show people = 120 with total = 2, then with total = 3: the 120 filled the first parameter, people. The run prints [0,0], and there is no error. After the edit, the stops show total = 120 with people = 2, then with people = 3, and the second list is [60,40].

Expected output

[30,50]
[0,0]

03 / Sections

How do you give an operator one of its two arguments?

An operator such as / takes two arguments, one on each side. Map, filter, and reduce used operator sections such as (+ 10): an operator in parentheses with one side filled in. For an operator whose result depends on the order, the side you fill matters. (/ 2) fills the right side, so (/ 2) 8 is 8 / 2, which is 4.0. (2 /) fills the left side, so (2 /) 8 is 2 / 8, which is 0.25. Each argument goes into the gap.

A function name between backticks is used as an operator, so it has sections too: (`div` 2) 9 is 9 `div` 2, which is 4. An operator alone in parentheses, such as (-) or (/), is an ordinary function of two arguments, and a partial application of it fills the left side: (-) 10 is the function that gives 10 minus its argument.

Minus is the exception to sections. (- 1) is not a section: Haskell reads it as the number minus one. In map (- 1) fees, GHC reports No instance for (Num (Double -> Double)) arising from a use of syntactic negation. The message means that GHC tried to make a negative number out of something that must be a function. Write subtract 1 instead: subtract 1 fee is fee - 1. subtract takes the amount to subtract first, so that it can be partially applied.

Main.hs
fees :: [Double]
fees = [4, 8]

-- the gap on the left: fee / 2
halves = map (/ 2) fees
-- [2.0,4.0]
-- the gap on the right: 2 / fee
inverted = map (2 /) fees
-- [0.5,0.25]
-- subtract 1 fee is fee - 1
lower = map (subtract 1) fees
-- [3.0,7.0]

A section fills one side of an operator, and each argument goes into the gap; (- 1) is a number, so write subtract 1.

Precisely: why minus is different

The Haskell 2010 Report gives - two meanings: subtraction between two values, and negation in front of one value, which it reads as negate. The Report states that (- e) is not a section but an application of negation. (+ (-1)) and \fee -> fee - 1 also work in place of subtract 1.

Experiment

Fill each side of an operator

(/ 2) leaves the left side open, so each fee goes on the left: fee / 2. (2 /) leaves the right side open: 2 / fee. (- 1) is the number minus one, so map receives a number where it needs a function, and GHC looks for a way to use a function as a number.

Why it matters Each element goes into the gap of a section. For minus, write subtract.

Predict the three printed lists. Then change subtract 1 on line 2 to (- 1) and predict what GHC says.

lowerAll :: [Double] -> [Double]
lowerAll fees = map (subtract 1) fees

main :: IO ()
main = do
  let fees = [4, 8]
  print (map (/ 2) fees)
  print (map (2 /) fees)
  print (lowerAll fees)
What to look for

GHCi stops only in your own definitions, so there is no stop inside a section. At the first stops on line 7, fees shows _ (unevaluated); after the stop on line 6, it shows [4.0,8.0]. The run prints [2.0,4.0] for (/ 2) and [0.5,0.25] for (2 /). The last stops are in lowerAll on line 2, with fees = [4.0,8.0], and the last line printed is [3.0,7.0]. After the edit, GHCi refuses to load the program: No instance for (Num (Double -> Double)) arising from a use of syntactic negation, with the mark under (- 1) on line 2.

Expected output

[2.0,4.0]
[0.5,0.25]
[3.0,7.0]

04 / Pairs

What changes when two arguments arrive as one pair?

A tuple groups a fixed number of values: (2, 3) is one value, a pair. A function can take a pair instead of two arguments. priceOf (hours, people) = … takes one argument, a pair, and its type is (Int, Int) -> Int. Such a function cannot be partially applied, because it has only one argument. Giving a pair to a curried function is a type error: tourPrice (2, 3) gives Couldn't match expected type ‘Int’ with actual type ‘(a0, b0)’.

uncurry turns a curried function into one that takes a pair: uncurry tourPrice (2, 3) is tourPrice 2 3. curry goes the other way: curry priceOf 2 3 is priceOf (2, 3). Their types say it: uncurry :: (a -> b -> c) -> (a, b) -> c, and curry :: ((a, b) -> c) -> a -> b -> c.

uncurry is useful when values already come in pairs, such as a list of bookings, or the pairs that zip makes from two lists: zip [2, 3] [3, 1] is [(2,3),(3,1)]. map (uncurry tourPrice) gives the first part of each pair to the first parameter. zipWith tourPrice [2, 3] [3, 1] gives the same result without making the pairs.

Main.hs
priceOf :: (Int, Int) -> Int
priceOf (hours, people) = hours * 6 * people + 4

-- each booking is (hours, people)
bookings = [(2, 3), (3, 1)]
prices = map (uncurry tourPrice) bookings
-- [40,22]
same = curry priceOf 2 3
-- 40

A function on a pair takes one argument; uncurry and curry convert between the two forms, and the first part of a pair always fills the first parameter.

Coming from Python: tour_price(*booking) spreads a tuple into two arguments when the function is called. In Haskell the conversion is a function, uncurry, and its type says what it does.

Precisely: when a pair is the better choice

Haskell code usually prefers curried functions, because they can be partially applied. A pair parameter is common where two values belong together, such as the two coordinates of a point, or where they always arrive together, as in a list of pairs.

Experiment

Apply a two-argument function to pairs

uncurry tourPrice is a function of one pair. It applies tourPrice to the first part of the pair, then applies the result to the second part. map tourPrice bookings would give tourPrice a whole pair as its hours, and a pair is not an Int.

Why it matters When values arrive in pairs, use uncurry, and check that the order in the pair matches the order of the parameters.

Each booking is a pair: hours, then people. Predict the printed list. Then change uncurry tourPrice on line 7 to tourPrice and predict what GHC says.

tourPrice :: Int -> Int -> Int
tourPrice hours people = hours * 6 * people + 4

main :: IO ()
main = do
  let bookings = [(2, 3), (3, 1)]
  print (map (uncurry tourPrice) bookings)
What to look for

GHCi stops three times on line 2 for each booking. There, hours and people show _ (unevaluated): uncurry passes the first part and the second part of the pair as work still to do, and GHCi stops before that work is done. The output grows to [40, and the run prints [40,22]: 2 hours for 3 people, then 3 hours for 1 person. After the edit, GHCi refuses to load the program: Couldn't match type ‘(a0, b0)’ with ‘Int’, with Expected: [Int] and Actual: [(a0, b0)], and the mark under bookings.

Expected output

[40,22]

05 / Point-free style

When should a definition leave out its argument?

When a definition’s last parameter appears only at the end of both sides, it can be left out. halveAll fees = map (/ 2) fees and halveAll = map (/ 2) define the same function: both sides are applied to fees, so removing fees from both changes nothing. This is called point-free style; “point” is an old word for an argument.

Point-free style is clear when what remains is one partial application or one section, as in halveAll. It soon becomes hard to read. The . operator joins two functions: (f . g) x is f (g x). The topic Composition and data transformation, later in this section, covers it. With . and sections, splitCost = (. length) . div . sum is a correct definition, but few readers can say what it computes. With its parameters named, it is splitCost costs people = sum costs `div` length people.

A useful test: if a reader must work out the type to know what a definition does, name the parameters. Keep a type signature on every point-free definition, because it tells the reader what the missing arguments are.

Main.hs
halveAll :: [Double] -> [Double]
halveAll = map (/ 2)

-- point-free, and hard to read:
splitCost :: [Int] -> [String] -> Int
splitCost = (. length) . div . sum
-- the same function, with named parameters:
-- splitCost costs people = sum costs `div` length people

Leave out the last argument when one partial application or section remains; name the parameters when the reader would need the type to follow the code.

Precisely: the monomorphism restriction

A top-level definition with no parameters and no type signature, such as halveAll = map (/ 2), falls under the monomorphism restriction of the Haskell 2010 Report. GHC gives it one type, chosen from how the program uses it or by defaulting to Double, instead of a type that works for every kind of fractional number. With halveAll fees = … or with a type signature, the restriction does not apply.

06 / In production

What goes wrong with partial application in real programs?

Four mistakes with partial application are common in real Haskell code.

A section the wrong way round. map (/ 2) prices halves each price; map (2 /) prices divides 2 by each price and still type-checks. The same holds for (`div` n) and (n `div`). Read the gap as the place each element goes.

(- 1) used as a function. map (- 1) counts does not subtract one: (- 1) is the number minus one, and GHC reports No instance for (Num (Int -> Int)) arising from a use of syntactic negation. The message names negation and Num, not the section, so it is easy to misread. Write subtract 1.

A partial application stored with its arguments in the wrong order. Below, fromHotel was meant as “800 metres from the hotel, at any speed”. walkMinutes takes the speed first, so fromHotel 80 is 80 `div` 800, which is 0. Both are Int, so GHC accepts it, and the stored name is used far from the definition. When you define such a function, put the argument that changes least first, and give the parameters names that say their role.

Point-free code nobody can read. A chain of . and sections that saves one name costs every later reader time. When a review needs the type to explain a line, give the line its parameters back.

Main.hs
walkMinutes :: Int -> Int -> Int
walkMinutes speed metres = metres `div` speed

-- meant: 800 metres, at any speed
-- gives: a speed of 800, for any distance
fromHotel = walkMinutes 800

Read the gap in each section, write subtract for minus, check the parameter order before you store a partial application, and name the parameters when point-free code needs its type to be read.

07 / Try it

Can you predict what each partial application gives?

Decide which parameter each partial application fills, and which side each section fills, before you predict.

Main.hs
fee :: Int -> Int -> Int
fee base people = base + 3 * people

main :: IO ()
main = do
  let small = fee 10
      late = flip fee 2
      halves = map (`div` 2) [9, 4]
      lower = map (subtract 3) [5, 10]
  print (small 4, late 20)
  print (halves, lower)

What are the two printed lines?

Reveal the reasoning

(22,26), then ([4,2],[2,7]). small is fee with base 10, so small 4 is 10 + 3 * 4, which is 22. flip fee 2 gives 2 to the second parameter, people, so late 20 is fee 20 2, which is 26. (`div` 2) leaves the left side open, so each element is divided by 2, without the remainder: 9 gives 4 and 4 gives 2. subtract 3 takes 3 away from each element: 5 gives 2 and 10 gives 7. With (- 3) instead of subtract 3, the program would not compile.

Practice tickets

  1. HS-301 · Large top-ups show 0 rides → Free
  2. HS-302 · Last-minute packages cost 0 euros, and the cheapest costs more → Sign in · Pro
  3. HS-303 · Checkout totals differ from the prices on the cottage pages → Sign in · Pro

Keep exploring

Stack and heap →

Primary references