First concepts

Expression vs Statement

A statement will produce a side effect. An expression will produce a value... and a possible side effect (that we should avoid).

  • F♯ is a functional, expression-based language only.

  • In comparison, C♯ is an imperative language, based on statements, but includes more and more syntactic sugar based on expressions:

⚖️ Benefits of expressions over instructions

  • Conciseness: less visual clutters → more readable

  • Composability: composing expressions is like composing values

  • Understanding: no need to know the previous instructions to understand the current one

  • Testability: pure expressions are easier to test

    • Predictable: same inputs mean same outputs

    • Isolated: shorter Arrange/Setup phase in tests, no need for mocks

Everything is an expression

  • A function is declared and behaves like a value

    • We can pass it as parameter or return it from another function (1)

  • The control flow building blocks are also expressions

    • if … then/else , match … with

    • for … in, for … to, while … do just return "nothing" (2)

Notes

  • (1) See 1st-class citizens, high-order functions 📍

  • (2) Except in collection comprehensions 📍

Consequences

  • No void → Best replaced by the unit type 📍

  • No Early Exit

    • In C#, you can exit a function with return and exit a for/while loop with break.

    • In F♯ these keywords do not exist.

Early exit alternatives

The most questionable solution is to raise an exception 💩 (see StackOverflow)

One solution in imperative style is to use mutable variables 😕

The most recommended and idiomatic solution in functional programming is to use a recursive function 📍

Typing, inference and ceremony

The ceremony is correlated to the typing weakness

Language
Typing strength
Inference strength
Ceremony

JS

Low (dynamic typing)

×

Low

C♯

Medium (static nominal)

Low

High

TS

Strong (static structural + ADT)

Medium

Medium

F♯

Strong (static nominal + ADT)

High

Low

🔗 Zone of Ceremony by Mark Seemann

Type inference

Goal: write type annotations as little as possible

  • Less code to write 👍

  • Compiler ensures consistency

  • IntelliSense helps with coding and reading

Type inference in C♯

  • Method parameters and return value ❌

  • Variable declaration: var o = new { Name = "John" } ✔️

  • Lambda as argument: list.Find(i => i == 5) ✔️

  • Lambda declaration: var f3 = () => 1; ✔️ in C# 10 (limited)

  • Array initialisation: var a = new[] { 1, 2 }; ✔️

  • Generic classes:

    • constructor: new Tuple<int, string>(1, "a")

    • static helper class: Tuple.Create(1, "a") ✔️

  • C♯ 9 target-typed expression StringBuilder sb = new(); ✔️

Type inference in F♯

Hindley–Milner method

  • Able to deduce the type of variables, expressions and functions in a program without any type annotation

  • Based on both the implementation and the usage

Example:

Automatic generalization in F♯ inference

If something can be inferred as generic, it will be → Open to more cases 🥳

Generic type parameter notation

  • starts with an apostrophe ' (a.k.a. tick)

  • can be in camelCase ('a) or PascalCase ('A)

  • C♯ TXxx → F♯ 'xxx or 'Xxx

Inference vs type annotation

  • Pros:

    • code terser

    • automatic generalization

  • Cons:

    • we can break code in cascade

    • inference limited:

      • an object type cannot be determine by the call to one of its members (1) → exception: Record types 📍

      • sensible to the order of declaration (2)

(1) Example:

(2) Example:

Last updated

Was this helpful?