liveBook · Manning

Higher-order function

As explained in F# in Action: A Revised Edition of Get Programming with F#

A higher-order function is a function that takes functions as arguments, returns a function, or both.

A higher-order function (HOF) accepts a function as an argument, letting one piece of logic stay fixed while a caller supplies the behavior that varies. This replaces hard-coded actions such as console output with a value that can write to a file, database, or another destination. section 7.1

let executeCalculation logger a b =
    let answer = a + b
    logger $"Adding {a} and {b} = {answer}"
    answer

The logger is an input function: calculation remains unchanged while output becomes selectable at the call site. section 7.1

This is a lightweight alternative to an interface or base class when the required variation is simply behavior. The supplied function must match the expected signature; F# infers that signature from how the argument is used. Parentheses around a function argument help distinguish it from the HOF’s ordinary arguments. section 7.1 section 15.2.2

Inject a printer

A customer-description function can receive a printer and use it wherever it would otherwise call printfn. Pass a console printer in one case and a file-writing printer in another. For a tiny one-off behavior, pass a lambda inline; name the function separately when it grows beyond a small expression. section 15.2.2 section 7.1

HOFs also power collection processing. List.map receives a transformation function and a list, then returns a list of transformed values. Reading its signature—('T -> 'U) -> 'T list -> 'U list—shows the function input, collection input, and resulting collection in order. section 7.2.3 section 7.6

For testing, make a side effect an argument. An invoice workflow can accept save instead of directly calling SQL; production supplies the real save function, while a test supplies a fake. This tests surrounding business logic without a mocking library. section 14.3.6

Use higher-order functions deliberately:

  • Choose them when a stable algorithm needs one varying behavior, such as logging, printing, saving, or transforming data. section 7.1
  • Read function signatures before combining collection functions; the function argument often explains why a composition does or does not fit. section 7.2.3
  • Prefer data-in, data-out functions where possible, then introduce a HOF when varying behavior is genuinely needed. section 15.2.5
  • Avoid accumulating many function arguments in one function, because the resulting signatures become difficult to understand. section 15.2.2 section 15.2.5

A function argument may perform I/O, so its presence prevents a compiler guarantee of purity. When purity is required, separate data generation from external behavior: return the text first, then compose that pure function with a specific printer at the edge. section 15.2.3

How F# in Action: A Revised Edition of Get Programming with F# covers it

How deep it goes
Defines function arguments, explains inferred signatures, and applies them to collections, testing, and purity trade-offs.
Examples
Uses configurable calculation logging, customer printers, List.map transformations, and a fake database save for invoice tests.
What it assumes
Assumes basic F# function signatures while explaining function-valued arguments and inferred contracts from usage.
What you can do after
Apply it by passing matching functions to vary behavior, compose transformations, and isolate testable side effects.
How it connects
Connects type inference, lambda functions, List.map, pipelines, side effects, unit testing, and pure functions.

Also explained in

Compare how 14 books explain higher-order function →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of F# in Action: A Revised Edition of Get Programming with F#. The section links point to the parts of the book it draws on.