Skip to content
Higher-Order Functions and Composition
step 1/7

Reading — step 1 of 7

Learn

~3 min readHigher-Order, Modules, Mutable

F# functions are first-class values. Combined with currying and composition operators, they make pipelines natural. Microsoft's F# docs treat this as central to the language's style.

Functions as values

let double x = x * 2
let f = double                          // alias
f 5                                      // 10

let apply g x = g x
apply double 7                          // 14

Functions can be passed, returned, stored. Type signatures use ->: int -> int for one-arg.

Currying

All F# functions are curried by default:

let add a b = a + b                     // int -> int -> int
let add5 = add 5                         // int -> int (partial)
add5 10                                  // 15

Calling add 5 returns a function. Apply more args to fully evaluate.

The pipeline operator |>

let result =
    [1..10]
    |> List.filter (fun x -> x % 2 = 1)
    |> List.map (fun x -> x * x)
    |> List.sum
// 1 + 9 + 25 + 49 + 81 = 165

x |> f is f x. F#'s signature operator — used everywhere for top-down value flow.

<| is the reverse pipe: f <| x is f x. Less common but useful for one-shot avoiding parens:

printfn "%d" <| List.sum [1; 2; 3]

Composition operators

>> — forward composition:

let addThenDouble = (+) 1 >> (*) 2
addThenDouble 5                          // (5 + 1) * 2 = 12

f >> g is fun x -> g (f x). Reads left-to-right. Builds reusable composed functions.

<< — backward composition:

let doubleThenAdd = (+) 1 << (*) 2
doubleThenAdd 5                          // (5 * 2) + 1 = 11

f << g is fun x -> f (g x). Reads right-to-left.

Pipeline vs composition

// Pipeline — value flow:
5 |> double |> string
// => "10"

// Composition — function-building:
let stringDouble = double >> string
stringDouble 5
// => "10"

Pipeline for ad-hoc transformation of a value. Composition for building reusable composite functions.

Common combinators

id x                                     // x — identity
fst (a, b)                               // a — first of tuple
snd (a, b)                               // b — second

List.map (fun x -> x * 2)               // partial application
List.filter (fun x -> x > 0)
List.fold (fun acc x -> acc + x) 0

// Unflipped vs flipped:
List.map double [1; 2; 3]               // [2; 4; 6]
[1; 2; 3] |> List.map double            // same; pipeline-friendlier

Notice List.map takes the function FIRST, list SECOND. Pipeline applies the list — so list |> List.map fn works naturally.

Higher-order helpers

List.collect f xs                        // map then concat — flatMap
List.partition pred xs                   // (yes-list, no-list)
List.tryFind pred xs                     // Option<T>
List.fold f init xs                      // left fold
List.foldBack f xs init                  // right fold (note arg order)
List.iter (printfn "%d") [1; 2; 3]       // for side effects

The *Back family (foldBack, scanBack) takes the list FIRST and accumulator LAST — opposite of fold. Easy to mix up.

Common mistakes

  • Using f x when you meant f(x) — F# doesn't need parens; f x is the call. Parens for grouping only.
  • Forgetting to fully applyadd 5 with no second arg is a function value, not 5+nothing.
  • >> vs |> — easy to confuse. >> composes functions; |> applies value through function.
  • fold vs foldBack argument order — different. fold takes (f, init, list); foldBack takes (f, list, init).
  • Passing methods directly — .NET methods are not F# functions. Wrap in lambda: (fun x -> obj.Method x) or use obj.Method (works in many cases via auto-conversion).

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…