F# Guide — Functions: First-Class Citizens in F#
In this tutorial, you will learn about F# Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
Functions in F# are first-class values that can be passed as arguments to other functions, returned as results, and composed together to build complex operations from simple, reusable building blocks.
What You'll Learn
- Defining functions with let bindings
- Type annotations and type inference
- Partial application and currying
- Higher-order functions
- Function composition and pipelines
Why It Matters
Functions are the fundamental unit of abstraction in F#. Understanding first-class functions, partial application, and composition enables concise, reusable, and testable code. Durga Antivirus Pro uses function composition for its analysis pipeline.
Real-World Use
F#'s function composition is used in data processing pipelines, web request handling, and financial calculations where operations are naturally chained.
flowchart LR
A["Functions"] --> B["Definition"]
B --> C["Currying"]
C --> D["HOFs"]
D --> E["Composition"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Defining Functions
// Basic function
let add x y = x + y
// With type annotations
let multiply (x: int) (y: int): int = x * y
// Single argument
let square x = x * x
// No arguments (unit)
let greet () = printfn "Hello!"
Type Inference
// F# infers types from usage
let add x y = x + y
// val add: x: int -> y: int -> int
let concat a b = a + b
// val concat: a: string -> b: string -> string
let genericAdd x y = x + y // Error: ambiguous
Currying and Partial Application
// All F# functions are curried by default
let add x y = x + y
// Partial application
let addFive = add 5
let result = addFive 3 // 8
// Real example
let multiply x y = x * y
let double = multiply 2
let triple = multiply 3
[1; 2; 3] |> List.map double // [2; 4; 6]
Higher-Order Functions
// Function as parameter
let applyTwice f x = f (f x)
let result = applyTwice (fun x -> x * 2) 5 // 20
// Function as return value
let createAdder x = fun y -> x + y
let add10 = createAdder 10
add10 5 // 15
Pipelines
// Forward pipe operator |>
let result = 5 |> square |> add 10 |> multiply 2
// Without pipes (nested calls)
let result2 = multiply 2 (add 10 (square 5))
// Pipes make the data flow clear
let processed =
data
|> List.filter (fun x -> x > 0)
|> List.map square
|> List.sum
Function Composition
// Composition operator >>
let addThenDouble = add 5 >> multiply 2
addThenDouble 3 // (3 + 5) * 2 = 16
// Reverse composition <<
let doubleThenAdd = multiply 2 >> add 5
doubleThenAdd 3 // (3 * 2) + 5 = 11
Anonymous Functions
// Lambda syntax
List.map (fun x -> x * x) [1; 2; 3]
// Multiple arguments
List.fold (fun acc x -> acc + x) 0 [1; 2; 3]
// Pattern matching in lambda
List.choose (fun x -> if x > 0 then Some x else None) [-1; 0; 1; 2]
Common Mistakes
1. Forgetting partial application order
List.map f [1;2] applies f to each element. List.map takes the function first, then the list.
2. Missing parentheses for unit
Functions with no arguments need (): let f () = ... not let f = ....
3. Confusing pipe and composition
|> pipes a value into a function. >> composes two functions. They serve different purposes.
4. Overusing lambdas
If a lambda just calls a named function, use partial application: List.map (add 5) not List.map (fun x -> add 5 x).
5. Ignoring generic type constraints
Some operations (like +) are not generic. You may need to specify types or use inline functions.
Practice Questions
1. What does currying mean in F#? All functions take one argument and return another function (or value). Multiple arguments are simulated through nested functions.
2. What does the pipe operator |> do? It passes the left operand as the last argument to the right operand function, enabling readable data flow.
3. When would you use >> vs |>?
>> composes two functions into one. |> applies a value to a function. Use >> to build new functions, |> to Process data.
Challenge: Write a pipeline that filters even numbers, squares them, and sums the result using only function composition and pipes.
FAQ
{{< faq question="Are F# functions pure by default?" >}} No. F# is not purely functional. Functions can have side effects. Immutability is the default, but mutability is available when needed. {{< /faq >}}
{{< faq question="What is the difference between let and let rec?" >}}
let defines a function that cannot call itself. let rec defines a recursive function that can reference itself.
{{< /faq >}}
{{< faq question="Can functions be nested?" >}} Yes. F# supports nested functions that capture variables from the enclosing scope (closures). {{< /faq >}}
{{< faq question="What is inline in F#?" >}}
inline tells the compiler to inline the function at the call site, enabling generic arithmetic and avoiding function call overhead.
{{< /faq >}}
{{< faq question="How do I define generic functions?" >}}
F# infers generic types automatically. Use explicit type parameters with 'T notation for complex cases: let identity (x: 'T) = x.
{{< /faq >}}
Mini Project
Build a data processing pipeline using function composition:
let processData =
List.filter (fun x -> x > 0)
>> List.map (fun x -> x * 2)
>> List.sum
let data = [-5; 1; 2; -3; 4]
let result = processData data // (1+2+4)*2 = 14
What's Next
Now that you understand functions, explore immutable data and how F# applies immutability by default.
| Topic | Description | Link |
|---|---|---|
| F# Immutable Data | Immutability in F# | {{< ref "05-immutable-data" >}} |
| F# Pattern Matching | Pattern matching basics | {{< ref "06-pattern-matching" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro