jonnifyfsharp

Module C2.02

From mutable state to immutability & expressions

F# inverts two C# defaults: everything returns a value, and values do not change.

A C# developer reaches for var, mutates a field, and writes a void method out of statements that run for their effect. F# inverts both habits. First, everything is an expression: if, match, and try each return a value, and the last expression in a function is its result — there is no return keyword. Second, values do not change: let binds an immutable value, a record has no setters, and a changed version is a new copy made with { r with … }. Neither shift is a restriction. The first removes the gap between a statement and the value it computes; the second removes whole classes of bug — aliasing, and the accidental shared mutation that comes with it. This module covers both in three dives, and ends where F# does not invert C#: mutation is still available, but contained.

Two inverted defaults, one worked example

The cleanest way to feel the shift is to write the same tiny function in both languages. In C#, a method that classifies a number runs if as a statement and hands the result back with return. In F#, the if is the result — the function is one expression, and its value is the function's value.

C# — statements, return

// a method built from statements
static string Grade(int n) {
    if (n >= 60) return "pass";
    return "fail";
}

F# — one expression

// the if IS the result; no return
let grade n =
    if n >= 60 then "pass" else "fail"
// val grade : int -> string
grade 75                          // val it : string = "pass"

The C# method computes a value and then returns it; the F# function is the value. That gap closing is the whole of the first dive.

The module, in three dives

The three dives below move from expressions, to immutability, to the contained mutation F# still allows. Select one to read its summary and the one idea it adds.

The three dives · select one

C2.02.1 expressions C2.02.2 immutable by default C2.02.3 mutation, contained return a value kept local
C2.02.1 · Expressions, not statements

if, match, and try are expressions that return a value. The last expression is the result; there is no return. A void method becomes a function returning unit.

You learn to read every construct as a value, and to spot the unit return

Open this dive · or pick another above

Everything returns a value; values do not change; and where mutation is needed, it stays inside the function. Those three facts retrain the C# reflexes that fight F#.

The three dives

Read them in order. Each stands on a C# the reader knows beside its idiomatic F#, and on valid, runnable F#: every val it : echo below was produced by dotnet fsi.

live module hub built dive ready soon specified

References

Sources

Related in this course