← Fundamentals
Guided lesson 6 questions · 6 languages

Function calls

Arguments in, a result out, and back to the code that made the call.

Applying a function to arguments describes a result. The parameters become new names for the arguments, and Haskell computes the body, and each argument, only when that result is needed.

Before you begin

Why this matters

Function application is almost all of a Haskell program. If you know what it computes and when, you can predict a program’s result and output, and see why an expression that would fail can be passed safely.

What this helps you reason about

  • Read an application as a description of a result, not as a command that runs at once.
  • Predict which arguments are computed, and how often.
  • Pass a function to another function, and apply a function to fewer arguments than it takes.

A Haskell function is a definition that turns arguments into a result. Applying it does not start a timeline of steps: the result is computed when something needs it. This lesson follows one application, and the arguments it receives, from definition to result.

Already know this? The short version
  • area 3 4 applies area to two arguments, matched to parameters by position; nothing is computed until the result is demanded, and then the equation’s right side gives it.
  • Arguments are not evaluated before the body: a parameter names its argument unevaluated, so an argument that is never needed, even 10 `div` 0, never runs, and one used twice is computed once.
  • There is no return statement and no caller to change: the right side is the result, and inside IO, return x builds an action that produces x without ending anything.
  • Functions are values and are curried: area 3 is a function waiting for height, and map, filter, and other functions take functions as arguments.

Go to the final check· Go to the practice tickets

Words in this lesson (5)
function application
Writing a function followed by its arguments, separated by spaces, such as area 3 4. It describes the function’s result for those arguments.
argument
An expression a function is applied to, such as 3 in area 3 4. It is computed only when the function’s result needs it.
parameter
A name in a function’s equation, such as width in area width height = width * height. In each application it names the matching argument, matched by position.
non-strict evaluation
The Haskell rule that an expression is evaluated only when its value is needed. A binding that nothing needs is never evaluated. GHC implements the rule as lazy evaluation: a named value is usually computed at most once and then shared.
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.

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 / The call

What happens when a function is applied?

A function is a definition that gives a result for its arguments. area width height = width * height defines area. area 3 4 applies area to the arguments 3 and 4: it is written without parentheses or commas. In the equation, width and height are the parameters: names for the arguments of this application.

Applying a function does not run it at once. let size = area 3 4 gives a name to the application. When printing needs size, GHC uses area’s equation with width 3 and height 4, and the result, 12, is the value of size. Nothing waits at a call and nothing comes back to it: the place that needed the value receives it.

Main.hs
area :: Int -> Int -> Int
area width height = width * height

main :: IO ()
main = do
  let size = area 3 4
  putStrLn ("after " ++ show size) -- after 12

A picture of what size depends on and when it is computed. It is not a timeline of calls or a memory layout.

A conceptual model, not a physical memory layout or live code execution.

1 / 3

Name the application

size
area 3 4

size is defined as area 3 4. Nothing has been computed.

An application describes a result; it is computed when something needs it.

Coming from JavaScript: area 3 4 is a call with two arguments; area (3, 4) would pass one argument, a pair, and is a type error for this area.

Precisely: no call stack picture

GHC does not evaluate Haskell by pushing one frame per application the way C does. The stepper shows where evaluation is happening and the names visible there, one location per stop, not a stack of waiting calls.

Experiment

Follow an application to its result

let size = area 3 4 gives a name to the application. Nothing computes it until printing needs size. Then area’s equation is used with width 3 and height 4, and the result, 12, is the value of size.

Why it matters In Haskell, writing an application does not run anything by itself. The result is computed when something needs it, and the place that needs it receives the value.

Predict whether area is applied before or after the text after is printed. Then change let size = area 3 4 to let size = area 3 4 + area 5 2 and predict how many times GHCi stops in area.

area :: Int -> Int -> Int
area width height = width * height

main :: IO ()
main = do
  putStrLn "before"
  let size = area 3 4
  putStrLn ("after " ++ show size)
What to look for

GHCi shows one location per stop, not a stack of frames. At the stops on the last line, size shows _ (unevaluated): the let line named the application but did not compute it. The output already shows after when GHCi stops in size and then in area, with width 3 and height 4. putStrLn wrote the text it had, and show size then needed the value, so area was applied only then. After the edit, GHCi stops in area twice, first with width 3, then with width 5, and the run prints after 22.

Expected output

before
after 12

02 / Arguments

When are the arguments computed?

An argument is computed only when the function’s result needs it. choose a b = a never uses b, so in choose left right, the expression right is never computed, even though it divides by zero. The program prints 3.

This is non-strict evaluation: an expression is evaluated only when its value is needed. There is no step where all arguments are computed before the body starts, and so no order to learn. What decides the order is what each result needs.

Main.hs
choose :: Int -> Int -> Int
choose a b = a

main :: IO ()
main = do
  let left = 1 + 2 :: Int
      right = 10 `div` 0 :: Int
  print (choose left right) -- 3

An argument that the result never needs is never computed.

Coming from Python: Python computes every argument before the call, so f(x, 1 / 0) fails even if f ignores its second parameter; Haskell computes it only if f’s result needs it.

Precisely: strictness analysis

GHC may compute an argument earlier when its analysis proves the function always needs it. This is never visible in the result: it does not happen for arguments that might not be needed.

Experiment

Pass an argument that is never needed

choose a b = a uses only its first parameter. Haskell computes an argument only when the result needs it, so the division in right is never done.

Why it matters A Haskell argument can be an expression that would fail, as long as nothing needs its value. Do not read a Haskell application as “compute every argument, then run the function”.

right divides by zero. Predict whether the program fails. Then change choose a b = a to choose a b = b and predict again.

choose :: Int -> Int -> Int
choose a b = a

main :: IO ()
main = do
  let left = 1 + 2 :: Int
      right = 10 `div` 0 :: Int
  print (choose left right)
What to look for

At the stop in choose, a shows _ (unevaluated): choose received its argument before anything computed it. GHCi then stops in left, because printing the result needs it, and it never stops in right. The run prints 3. After the edit, the result needs b, GHCi stops in right, and the run ends with *** Exception: divide by zero.

Expected output

3

03 / Parameters

What does the function receive?

Arguments are matched to parameters by position: the first argument goes to the first parameter, whatever the names are.

A parameter is a new name for its argument, not a copy of a computed value. In double score, n names score before anything has computed it. When n + n first needs a value, score is computed, and that one result is used for both n’s. Nothing can be changed through a parameter, because Haskell values never change.

Main.hs
double :: Int -> Int
double n = n + n

main :: IO ()
main = do
  let score = 2 + 3 :: Int
  print (double score) -- 10, computing 2 + 3 once

A parameter names its argument; the argument is computed at most once, when first needed.

Precisely: sharing is GHC’s practice

The Haskell Report promises only the result. GHC passes a parameter as a pointer to one shared, possibly unevaluated value (a thunk), which is why the work happens once. The optimizer may still copy small expressions into several places.

Experiment

Use a parameter twice

n is a new name for the argument score, not a fresh copy of a computed number. When n + n first needs a value, score is computed, and the result is kept for every later use.

Why it matters Using a parameter several times does not repeat the work of its argument. GHC computes a named value at most once in practice, and nothing can change it afterwards.

double uses n twice. Predict how many times GHCi stops in score. Then change print (double score) to print (double score, score) and predict again.

double :: Int -> Int
double n = n + n

main :: IO ()
main = do
  let score = 2 + 3 :: Int
  print (double score)
What to look for

At the stop in double, n shows _ (unevaluated): the parameter names the argument, which has not been computed yet. GHCi then stops in score once, and the run prints 10. After the edit, the output starts with ( before score is computed, GHCi still stops in score once, and the run prints (10,5).

Expected output

10

04 / Return

Where does the result of an application go?

A function has no return statement. The right side of the equation is the result, and it takes the place of the application wherever it is needed: entryFee 4 + entryFee 30 is 0 + 3. Guards choose between right sides: | age < 5 = 0 uses 0 when the test is true, and otherwise is a test that is always true.

The parameters exist only inside the equation. Nothing else comes back from an application: no changed variables, only the result.

Main.hs
entryFee :: Int -> Int
entryFee age
  | age < 5 = 0
  | otherwise = 3

total :: Int
total = entryFee 4 + entryFee 30 -- 3

The right side of the equation is the result of the application.

Coming from Java: return does not end anything in Haskell: inside a do block, return x is an action that produces x, and the lines after it still run.

05 / Functions as values

Can a function be passed to another function?

A function is a value, like a number. You can pass it to another function: applyTo triple 4 passes triple, and applyTo applies it. map triple [1, 2] applies triple to each element of a list. \n -> n + 1 is a lambda: a function without a name, written where it is used.

Applying a function to fewer arguments than it takes is called partial application. area 3 is a function that waits for the height, so map (area 3) [1, 2] gives [3,6]. The type Int -> Int -> Int can be read as Int -> (Int -> Int): a function that takes a width and returns a function of the height.

A function that uses names from the definitions around it is called a closure. How GHC keeps those values available is the subject of Stack and heap in the Runtimes module.

Main.hs
applyTo :: (Int -> Int) -> Int -> Int
applyTo step value = step value

triple :: Int -> Int
triple n = n * 3

results :: [Int]
results = [applyTo triple 4, applyTo (\n -> n + 1) 4]
-- [12,5]

A function can be passed as an argument, and area 3 is itself a function.

06 / Try it

Can you follow a fresh call?

Ask which arguments the result needs. The next lesson, Recursion, uses these two facts: each application has its own parameters, and its result is used at the place where the application is written. Its link is at the end of the page.

Main.hs
twice :: Int -> Int
twice n = n * 2

pick :: Int -> Int -> Int
pick a b = if a > 2 then a else b

main :: IO ()
main = print (pick (twice 2) (10 `div` 0))

What is printed? Is 10 `div` 0 ever computed?

Reveal the reasoning

4. pick needs a to test a > 2, so twice 2 is computed: 4. The test is true, so the result is a, and b is never needed. 10 `div` 0 is never computed, and the program does not fail. With pick (twice 1) instead, the test would be false, the result would need b, and the program would fail with divide by zero.

Practice tickets

  1. CALL-101 · Charge both rides to the bus pass → Free
  2. CALL-102 · Grow the plant for two weeks → Sign in · Pro
  3. CALL-103 · Number the orders at each coffee cart → Sign in · Pro

Keep exploring

Recursion →

Primary references