100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Functions in F#

Learn how F# treats functions as first-class values, covering let bindings, currying, partial application, and higher-order functions.

Core ConceptsBeginner8 min readJul 10, 2026
Analogies

What Makes Functions First-Class in F#

In F#, functions are first-class values, meaning they can be bound to names, passed as arguments, returned from other functions, and stored inside data structures exactly like an int or a string. A definition such as "let square x = x * x" creates a value called square whose type -- int -> int -- is inferred automatically by the compiler without any type annotation. Because F# uses the same let keyword for functions and ordinary values, there is no artificial split between 'variable' and 'function' declarations the way many other languages enforce.

🏏

Cricket analogy: Think of a batting order in cricket -- you can name a player like Rohit Sharma to a slot, but you could just as easily assign that slot a different player later; in F#, binding a function to a name with let is just as flexible as slotting any other value.

Currying: Every Function Takes One Argument

Every F# function that appears to take multiple arguments, such as "let add x y = x + y", is actually a single-argument function that returns another single-argument function -- this is called currying, named after logician Haskell Curry. The type signature int -> int -> int reads as 'takes an int, returns a function of int -> int', and the right-associative arrow confirms that applying add 5 alone is perfectly legal and produces a new function waiting for the second argument.

🏏

Cricket analogy: A bowler's over is broken into six individual deliveries rather than one bulk action, the way F# breaks add x y into two sequential single-argument steps -- first x, then y -- instead of one two-argument call.

fsharp
// Named function with inferred type: int -> int -> int
let add x y = x + y

// Partial application creates a new specialized function
let addFive = add 5
printfn "%d" (addFive 10)   // 15

// Higher-order function + pipe operator
let numbers = [1; 2; 3; 4; 5]
let result =
    numbers
    |> List.map (fun n -> n * n)      // square each element
    |> List.filter (fun n -> n > 10)  // keep squares greater than 10

printfn "%A" result   // [16; 25]

// Recursive function requires the `rec` keyword
let rec factorial n =
    if n <= 1 then 1
    else n * factorial (n - 1)

Partial Application

Because functions are curried, supplying fewer arguments than a function expects -- called partial application -- produces a new, specialized function rather than an error. Writing "let addFive = add 5" binds addFive to a function of type int -> int that always adds five, and this technique is the backbone of building small reusable pipeline stages instead of writing repetitive wrapper functions for every fixed argument combination you need.

🏏

Cricket analogy: A fielding captain who sets a fixed slip cordon for a specific bowler, like Jasprit Bumrah, and then only adjusts the remaining fielders per batter, mirrors partial application -- add 5 fixes one argument and leaves the rest open.

The pipe operator |> is defined simply as let (|>) x f = f x. It doesn't add any new capability beyond ordinary function application, but it lets you write data transformations in the order they execute, which reads far more naturally than nesting calls like List.filter p (List.map f xs).

Higher-Order Functions and the Pipe Operator

A higher-order function either accepts another function as a parameter or returns one as a result, and F#'s standard library leans on this heavily: "List.map (fun x -> x * 2) [1;2;3]" takes the doubling function as its first argument and applies it to every list element. Combined with the pipe operator |>, which feeds the value on its left into the function on its right, chains like "[1;2;3] |> List.map square |> List.filter (fun x -> x > 2)" read left-to-right in the same order the data actually flows.

🏏

Cricket analogy: A team analyst who feeds match data through a pipeline -- filter to only Virat Kohli's innings, then map to strike rate -- mirrors numbers |> List.filter |> List.map, where each stage transforms the previous stage's output.

Anonymous Functions (Lambdas)

An anonymous function, or lambda, is written with the fun keyword followed by parameters, an arrow, and a body, as in "fun x -> x * x", and is used whenever a short one-off function is needed without cluttering the surrounding scope with a named let binding. Lambdas close over variables from their enclosing scope automatically, so "fun y -> x + y" inside a function that already has x in scope captures that x by reference to its value at the time the closure is created.

🏏

Cricket analogy: A substitute fielder brought on for just one over, with no long-term squad role, mirrors an anonymous lambda used inline for a single List.map call without ever being named.

F# functions are not implicitly recursive -- a function that calls itself must be declared with let rec, not plain let. Forgetting rec on a self-referencing definition produces a compiler error because the function name is not yet in scope inside its own body when using ordinary let.

  • Functions in F# are first-class values bound with let, just like any other value.
  • All multi-argument functions are curried: int -> int -> int really means a chain of one-argument functions.
  • Partial application supplies fewer arguments than expected to produce a new, specialized function.
  • Higher-order functions like List.map and List.filter accept functions as arguments.
  • The pipe operator |> feeds a value into a function, enabling left-to-right pipelines.
  • Lambdas are written with fun params -> body for short, unnamed functions.
  • Self-referencing functions require the rec keyword: let rec factorial n = ...

Practice what you learned

Was this page helpful?

Topics covered

#Programming#FStudyNotes#FunctionsInF#Functions#Makes#Class#Currying#StudyNotes#SkillVeris#ExamPrep