Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C# & .NET (C# 14, .NET 10 LTS)  Module 12 – Delegates, lambdas, events and functional style

Functional techniques in C#

Write pure functions over immutable records, join them into pipelines with delegates and C# 14 extension members, and return results instead of throwing.

  • Intermediate
  • 25 minutes
  • Examples run with .NET 10.0.401
  • By MySmartCoPilot
Free core, Pro depth: 10 of 25 minutes free
Free: A pure function returns the same result for the same input and changes nothing · Functions are values: pass them, return them, join them. With a pass: 2 in-depth parts and 3 interview answers, about 15 minutes.

What you will learn

  • Write pure functions, and change immutable records and lists with with and SetItem
  • Compose functions into pipelines with delegates, partial application and an extension member
  • Model expected failures with a Result type, and chain or collect them with Bind and Combine
  • Separate a pure core from a program's side effects, and judge how far to take functional style in C#

Before you start

What you’ll learn · Before you start

What you will learn

  • Write pure functions, and change immutable records and lists with with and SetItem
  • Compose functions into pipelines with delegates, partial application and an extension member
  • Model expected failures with a Result type, and chain or collect them with Bind and Combine
  • Separate a pure core from a program's side effects, and judge how far to take functional style in C#

Before you start

On this page

A checkout priced the same cart twice and charged Rs 679, then Rs 656.1. Its function had changed the cart it was given and remembered a discount in a static field, so the second call saw a different world. Functional techniques remove that kind of bug: functions that only read their input, data that never changes, and small functions joined into bigger ones.

You will make a function pure, change immutable data by making new values, and join pricing rules into one function that you can read from left to right.

A pure function returns the same result for the same input and changes nothing

Microsoft’s guide to pure functions gives them two properties: no side effects, so nothing outside the function changes, and consistency, so the same input always gives the same output. purity.cs prices one cart with an impure function and with a pure one:

purity.cs: the same cart, an impure total and a pure one C# · purity/purity.cs
// The same checkout total worked out twice. The impure version changes the lines it is given and a static flag, so
// the same call gives another answer the second time; the pure one only reads its input. The prices, the 10 % offer
// and the Rs 50 first-order discount are made up for this lesson.
using System.Collections.Immutable;

var mutable = new List<MutableLine> { new("Tea 250 g", 2, 180m), new("Rice 5 kg", 1, 450m) };
Console.WriteLine($"impure: Rs {Impure.Total(mutable)}, then Rs {Impure.Total(mutable)} for the same cart");
Console.WriteLine($"  the cart's tea now costs Rs {mutable[0].Price} a pack");

ImmutableList<Line> cart = [new("Tea 250 g", 2, 180m), new("Rice 5 kg", 1, 450m)];
Console.WriteLine($"pure:   Rs {Pure.Total(cart, firstOrder: true)}, then Rs {Pure.Total(cart, firstOrder: true)} for the same cart");

// A change is a new value: with makes a new line, SetItem a new list. The old cart is as it was.
ImmutableList<Line> moreTea = cart.SetItem(0, cart[0] with { Quantity = 3 });
Console.WriteLine($"  3 packs of tea: Rs {Pure.Total(moreTea, firstOrder: true)}; the old cart still has {cart[0].Quantity} and costs Rs {Pure.Total(cart, firstOrder: true)}");

// An expression, not a statement: a switch expression picks the GST rate, and a throw expression rejects the rest.
Console.WriteLine($"GST on essentials: {Pure.GstPercent("essentials")} %, on standard goods: {Pure.GstPercent("standard")} %");

record Line(string Name, int Quantity, decimal Price);

class MutableLine(string name, int quantity, decimal price)
{
    public string Name { get; } = name;

    public int Quantity { get; } = quantity;

    public decimal Price { get; set; } = price;
}

static class Impure
{
    private static bool s_firstOrderUsed;

    // Takes 10 % off by changing each line's price, and gives Rs 50 off the first order it ever sees.
    public static decimal Total(List<MutableLine> lines)
    {
        foreach (MutableLine line in lines)
        {
            line.Price = line.Price * 90 / 100;
        }

        decimal total = lines.Sum(line => line.Quantity * line.Price);
        if (!s_firstOrderUsed)
        {
            s_firstOrderUsed = true;
            total -= 50m;
        }

        return total;
    }
}

static class Pure
{
    // Reads its arguments, changes nothing, and returns the same total for the same arguments.
    public static decimal Total(IReadOnlyList<Line> lines, bool firstOrder)
    {
        decimal subtotal = lines.Sum(line => line.Quantity * line.Price);
        decimal afterOffer = subtotal * 90 / 100;
        return firstOrder ? afterOffer - 50m : afterOffer;
    }

    public static decimal GstPercent(string category) => category switch
    {
        "essentials" => 5m,
        "standard" => 18m,
        _ => throw new ArgumentOutOfRangeException(nameof(category), category, "no GST rate for this category"),
    };
}

Output

impure: Rs 679, then Rs 656.1 for the same cart
  the cart's tea now costs Rs 145.8 a pack
pure:   Rs 679, then Rs 679 for the same cart
  3 packs of tea: Rs 841; the old cart still has 2 and costs Rs 679
GST on essentials: 5 %, on standard goods: 18 %

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run purity.cs

Impure.Total took 10 % off by changing each line’s price, and gave Rs 50 off the first order by setting a static flag. Both are side effect: A change a function makes outside itself while it runs: writing a field, changing an argument, printing, saving a file. A function with side effects can give another result the next time it is called., and both showed: the second call found the tea at Rs 162, took 10 % off again and gave no first-order discount, Rs 656.1. Step through the two calls:

Interactive · step by step Two calls of the impure total, one cart Step through Impure.Total(cart), called twice with the same cart. Watch what each call leaves behind for the next.
Two calls of the impure total, one cart, step 6 of 6. Showing Impure.Total, The cart's lines (tea Rs 145.8, rice Rs 364.5), s_firstOrderUsed (true), The total (call 2: Rs 656.1). In focus: s_firstOrderUsed, The total.changes pricessetsImpure.Totalcalled with the cartThe cart's linestea Rs 145.8, rice Rs 364.5s_firstOrderUsedtrueThe totalcall 2: Rs 656.1

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The first call reads the cart. The cart holds 2 packs of tea at Rs 180 and a bag of rice at Rs 450: Rs 810 in all. The static flag says no first order has been seen yet.
  2. It changes the prices. To take 10 % off, the function writes the new price into each line: tea Rs 162, rice Rs 405. The cart the caller passed in has changed.
  3. It sets the flag. This is the first order, so it takes Rs 50 off and sets the flag to true, for every call that comes after.
  4. The first total. 2 × 162 + 405 = 729, minus 50: the first call returned Rs 679.
  5. The second call, another cart. The same call, with the same cart variable, now reads tea at Rs 162 and takes 10 % off again: Rs 145.8 and Rs 364.5.
  6. The flag says no discount. The flag is still true, so there is no Rs 50 this time: 2 × 145.8 + 364.5 = Rs 656.1. Same call, same variable, another answer.

A pure function: A function that has no side effects and always returns the same result for the same arguments, because it depends on nothing but them: no clock, no shared field, no input it reads by itself. gets everything it needs as arguments, firstOrder included, and returned Rs 679 both times. Pure functions are easy to test, since a test needs only the arguments, and safe to run in parallel, since nothing is shared. Programs still read clocks and write files; a common design keeps those side effects in a thin shell around a pure core.

Immutable data makes purity the easy path. The cart is an ImmutableList<Line> of records: cart[0] with { Quantity = 3 } made a new line, and SetItem a new list, priced at Rs 841, while the old cart still had 2 packs and cost Rs 679. ImmutableArray<T> wraps one plain array, which every change copies, and a builder takes many changes in place before it freezes them into a new immutable collection. Expressions help too: GstPercent is one switch expression that returns a rate or throws, with nothing assigned along the way. The two versions of Total, side by side:

Behaviour Impure.Total Pure.Total
reads the lines and a static flag only its arguments
changes every line’s price, the flag nothing
the same call twice Rs 679, then Rs 656.1 Rs 679, then Rs 679

Checkpoint

Check: Pure functions and immutable data

  1. Question 1 of 2 Which of these methods is pure?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: static decimal Tax(decimal amount, decimal rate) => amount * rate / 100;

    A pure function has no side effects and gives the same output for the same input. Reading shared state breaks the second rule; changing arguments or writing output breaks the first.

  2. Question 2 of 2 moreRice = cart.SetItem(1, cart[1] with { Quantity = 2 }) gives 2 packs of tea at Rs 180 and 2 bags of rice at Rs 450. What does Pure.Total(moreRice, firstOrder: false) return?

    Type a number.

    A hint

    Subtotal first, then the 10 % offer; no first-order discount.

    Show the answer: Question 2 of 2

    Answer: 1134 rupees

    2 × 180 + 2 × 450 = 1,260, and 10 % off gives 1,260 × 90 / 100 = 1,134. The old cart still has one bag of rice, because SetItem and with made new values.

Functions are values: pass them, return them, join them

A higher-order function: A function that takes another function as an argument or returns one, such as a method with a Func parameter, or a method that returns a lambda. takes or returns another function, and compose.cs uses both kinds, then joins functions with a C# 14 extension member:

compose.cs: functions passed, returned and joined with Then C# · compose/compose.cs
// Functions as values: a method that takes a function, methods that return functions, and a C# 14 extension member,
// Then, that joins two functions into one. The prices and rates are made up for this lesson.
List<Line> cart = [new("Tea 250 g", 2, 180m), new("Rice 5 kg", 1, 450m)];

// A function that takes a function: Apply runs any rule on every line.
Console.WriteLine($"each line at 10 % off: {string.Join(", ", Apply(cart, PercentOff(10)))}");

// Functions that return functions: PercentOff(10) and AddGst(5) fix one argument now and wait for the amount.
Func<decimal, decimal> festival = PercentOff(10);
Func<decimal, decimal> gst = AddGst(5);
Console.WriteLine($"festival(810) = {festival(810m)}, gst(729) = {gst(729m)}");

// Then joins functions: each step's result is the next step's input, read left to right.
Func<IReadOnlyList<Line>, decimal> subtotal = Subtotal;
Func<IReadOnlyList<Line>, decimal> checkout = subtotal.Then(festival).Then(gst).Then(RoundToRupee);
Console.WriteLine($"checkout(cart) = Rs {checkout(cart)}");

// The same steps in another order are another function, with another result.
Func<IReadOnlyList<Line>, decimal> roundFirst = subtotal.Then(RoundToRupee).Then(gst).Then(festival);
Console.WriteLine($"rounding first = Rs {roundFirst(cart)}");

static IEnumerable<decimal> Apply(IEnumerable<Line> lines, Func<decimal, decimal> rule) =>
    lines.Select(line => rule(line.Quantity * line.Price));

static decimal Subtotal(IReadOnlyList<Line> lines) => lines.Sum(line => line.Quantity * line.Price);

static Func<decimal, decimal> PercentOff(int percent) => amount => amount * (100 - percent) / 100;

static Func<decimal, decimal> AddGst(int percent) => amount => amount + amount * percent / 100;

static decimal RoundToRupee(decimal amount) => Math.Round(amount, 0, MidpointRounding.AwayFromZero);

record Line(string Name, int Quantity, decimal Price);

static class FunctionExtensions
{
    // A C# 14 extension block: Then becomes a member of every Func<T, TResult>.
    extension<T, TResult>(Func<T, TResult> first)
    {
        public Func<T, TNext> Then<TNext>(Func<TResult, TNext> next) => value => next(first(value));
    }
}

Output

each line at 10 % off: 324, 405
festival(810) = 729, gst(729) = 765.45
checkout(cart) = Rs 765
rounding first = Rs 765.45

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run compose.cs

Apply takes a rule and ran it on each line: 324 and 405. PercentOff(10) returns a function that remembers the 10 and waits for an amount, so festival(810) gave 729: fixing some arguments now and the rest later is partial application: Fixing some of a function's arguments now and the rest later. In C# a method returns a lambda that remembers the fixed arguments in a closure, as PercentOff(10) returns a rule that waits for an amount., done here with a closure. Then is declared once in an extension block for every Func<T, TResult>, and f.Then(g) makes the function that runs f and passes its result to g. Joined into checkout, the subtotal, the offer, GST and rounding gave Rs 765. Watch the cart go through the four steps:

Interactive · stage by stage One cart through four joined functions Step through checkout(cart). Each stage is one function, and its result is the next one's input.
One cart through four joined functions, step 4 of 4. Showing Subtotal (gives 810), festival: 10 % off (gives 729), gst: add 5 % (gives 765.45), RoundToRupee (gives 765). In focus: RoundToRupee.Subtotalgives 810festival: 10 % offgives 729gst: add 5 %gives 765.45RoundToRupeegives 765765.45

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. The cart goes in. checkout is one Func that takes the cart. Its first function, Subtotal, adds the lines: 2 × 180 + 450 is 810.
  2. Then the offer. Then passes 810 to festival, the function PercentOff(10) returned, which remembers its 10: 810 × 90 / 100 is 729.
  3. Then GST. gst adds the 5 % it was made with: 729 + 36.45 is 765.45.
  4. Then rounding. RoundToRupee rounds 765.45 to Rs 765, and checkout(cart) returns it. Each function did one thing and changed nothing outside itself.

function composition: Joining two functions into one, so that the result of the first becomes the argument of the second: f then g is the function x => g(f(x)). makes the order visible and changeable: roundFirst used the same four functions with rounding first and gave Rs 765.45, because nothing rounded the result of GST and the offer. The same style covers failures: instead of throwing, a function can return a small Result value that holds either its value or an error, so the caller has to look before using it. A chain of such results stops at the first failure, while checks that stand alone can be collected, so that a form reports all its errors at once.

Key idea

Pass, return and join small pure functions, and a pipeline reads from left to right, can be tested one step at a time, and changes order only when you change it.

Checkpoint

Check: Functions as values, partial application and composition

  1. Question 1 of 2 subtotal.Then(PercentOff(20)).Then(AddGst(5)).Then(RoundToRupee) runs on the cart of compose.cs, whose subtotal is Rs 810. What does it return?

    Type a number.

    A hint

    Each function gets the result of the one before it, left to right.

    Show the answer: Question 1 of 2

    Answer: 680 rupees

    810 × 80 / 100 = 648, plus 5 % GST gives 680.40, and rounding gives Rs 680.

  2. Question 2 of 2 What does PercentOff(10) return?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A function that takes an amount and returns it with 10 % off

    A method that returns a function lets you fix some arguments now and supply the rest later: PercentOff(10) is a rule waiting for its amount.

Expected failures as values: a Result type with Map and Bind, measured against exceptions Pro: opens with a Pro, Premium or Ultimate pass

A sign-up form fails often, and every one of its failures is expected. The runs below carry those failures as values, report all of them at once, and measure what an exception per failure would cost instead.

In this part:

  • A small Result type, chained with Map and Bind
  • Collecting every error instead of stopping at the first
  • What an exception per failure costs, measured
  • Exercise: a KYC form that reports every error

4 runnable examples (C#) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

A functional core in an imperative shell, and where functional C# stops Pro: opens with a Pro, Premium or Ultimate pass

Pure functions pay off most when the code that reads clocks, databases and consoles stays at the edges. The last runs show that split, then the parts of functional style that C# leaves to you and what they cost.

In this part:

  • A functional core with the side effects at the edge
  • What immutable data costs, measured
  • Where C# stops, and what the functional style costs

3 runnable examples (C#) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Practise: a pure checkout and an immutable cart

pricing.cs checks two functions that return functions, a pure Checkout built from them, and a WithQuantity that changes an immutable cart by returning a new one. All eight checks fail on the starter:

pricing.cs: eight checks of pure pricing C# · practice/pricing.cs
// Practice: write the four methods of Pricing and Carts, then run this file until all eight checks pass. What each
// must do is in the comment above it. The prices and rates are made up for this exercise.
using System.Collections.Immutable;

ImmutableList<Line> cart = [new("Tea 250 g", 2, 180m), new("Rice 5 kg", 1, 450m)];

(string Check, Func<string> Run, string Expected)[] checks =
[
    ("PercentOff(10) takes 10 % off", () => Pricing.PercentOff(10)(1000m).ToString(), "900"),
    ("AddGst(18) adds 18 %", () => Pricing.AddGst(18)(1000m).ToString(), "1180"),
    ("Checkout applies the offer, then GST, then rounds", () => Pricing.Checkout(cart, offerPercent: 10, gstPercent: 5).ToString(), "765"),
    ("Checkout gives the same total twice", () => $"{Pricing.Checkout(cart, 10, 5)} and {Pricing.Checkout(cart, 10, 5)}", "765 and 765"),
    ("Checkout leaves the cart as it was", () => { Pricing.Checkout(cart, 10, 5); return $"{cart.Count} lines, {cart[0].Quantity} packs of tea"; }, "2 lines, 2 packs of tea"),
    ("WithQuantity returns a changed cart", () => Carts.WithQuantity(cart, "Tea 250 g", 3)[0].Quantity.ToString(), "3"),
    ("WithQuantity leaves the old cart as it was", () => { Carts.WithQuantity(cart, "Tea 250 g", 3); return cart[0].Quantity.ToString(); }, "2"),
    ("an unknown item gives back the same cart", () => ReferenceEquals(Carts.WithQuantity(cart, "Salt 1 kg", 1), cart) ? "same cart" : "another cart", "same cart"),
];

int passed = 0;
foreach (var (check, run, expected) in checks)
{
    string got;
    try
    {
        got = run();
    }
    catch (Exception e)
    {
        got = $"throws {e.GetType().Name}";
    }

    bool ok = got == expected;
    if (ok)
    {
        passed++;
    }

    Console.WriteLine(ok ? $"{check}: ok" : $"{check}: FAIL, {got} instead of {expected}");
}

Console.WriteLine($"{passed} of {checks.Length} checks pass");
return passed == checks.Length ? 0 : 1;

record Line(string Name, int Quantity, decimal Price);

static class Pricing
{
    // Returns a function that takes percent per cent off an amount.
    public static Func<decimal, decimal> PercentOff(int percent) => throw new NotImplementedException();

    // Returns a function that adds percent per cent of GST to an amount.
    public static Func<decimal, decimal> AddGst(int percent) => throw new NotImplementedException();

    // The subtotal of the lines, then the offer, then GST, then rounded to the rupee (midpoints away from zero).
    // Pure: it reads its arguments and changes nothing.
    public static decimal Checkout(IReadOnlyList<Line> lines, int offerPercent, int gstPercent) => throw new NotImplementedException();
}

static class Carts
{
    // Returns a cart in which the line with this name has the new quantity; the cart passed in does not change. When no
    // line has this name, returns the very same cart.
    public static ImmutableList<Line> WithQuantity(ImmutableList<Line> cart, string name, int quantity) => throw new NotImplementedException();
}

Output (exit status 1)

PercentOff(10) takes 10 % off: FAIL, throws NotImplementedException instead of 900
AddGst(18) adds 18 %: FAIL, throws NotImplementedException instead of 1180
Checkout applies the offer, then GST, then rounds: FAIL, throws NotImplementedException instead of 765
Checkout gives the same total twice: FAIL, throws NotImplementedException instead of 765 and 765
Checkout leaves the cart as it was: FAIL, throws NotImplementedException instead of 2 lines, 2 packs of tea
WithQuantity returns a changed cart: FAIL, throws NotImplementedException instead of 3
WithQuantity leaves the old cart as it was: FAIL, throws NotImplementedException instead of 2
an unknown item gives back the same cart: FAIL, throws NotImplementedException instead of same cart
0 of 8 checks pass

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run pricing.cs

Write the pricing functions

Copy pricing.cs. Return lambdas from PercentOff and AddGst. In Checkout, sum the lines, then apply the offer, GST and Math.Round(amount, 0, MidpointRounding.AwayFromZero). In WithQuantity, find the line’s index with FindIndex, return cart itself when it is −1, and otherwise cart.SetItem(index, cart[index] with { Quantity = quantity }). Run dotnet run pricing.cs until it prints 8 of 8 checks pass.

Then check yourself on pure functions and composition:

Practice that adapts to you

Practise this lesson

6 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 6 Which two properties make a function pure?

    Choose one answer.

    Show the answer: Question 1 of 6

    Answer: It has no side effects, and the same input always gives the same output

    No side effects and consistency: nothing outside changes, and the result depends only on the arguments.

  2. Question 2 of 6 Why did the second call of Impure.Total return Rs 656.1 for the same cart?

    Choose one answer.

    Show the answer: Question 2 of 6

    Answer: The first call had changed the line prices and set the static flag

    Side effects make a function depend on its own history: what earlier calls left behind changes later results.

  3. Question 3 of 6 A pricing method reads DateTime.Now to give a weekend offer, and its tests pass on weekdays only. How do you make it pure?

    Choose one answer.

    Show the answer: Question 3 of 6

    Answer: Pass the day in as an argument, and let the caller read the clock

    Anything a function reads from the world, the clock included, becomes an argument of a pure function; the code around it reads the world once and passes the value in.

  4. Question 4 of 6 What does f.Then(g) return?

    Choose one answer.

    Show the answer: Question 4 of 6

    Answer: A function that runs f and then g on f's result

    Composition turns two functions into one, the output of the first becoming the input of the second.

  5. Question 5 of 6 Why did roundFirst give Rs 765.45 when checkout gave Rs 765?

    Choose one answer.

    Show the answer: Question 5 of 6

    Answer: Rounding ran first, and GST and the offer then made paise again from the rounded amount

    Order is part of a composed function's meaning. Writing the order down in one place makes it a decision.

  6. Question 6 of 6 Which of these is a higher-order function?

    Choose one answer.

    Show the answer: Question 6 of 6

    Answer: static IEnumerable<decimal> Apply(IEnumerable<Line> lines, Func<decimal, decimal> rule)

    A function that takes or returns another function is higher-order: Apply takes one, and PercentOff returns one.

Interview questions

Fresher · What is a pure function, and why is it easier to test?

A pure function changes nothing outside itself, not its arguments, a field or a file, and gives the same result for the same arguments, because it depends on nothing else, not the clock and not a static setting. Its test needs only arguments and an expected result: no shared state to set up, no mocks, no order between tests, and the same outcome on every run. Nothing is shared, so it is also safe to call from several threads at once. Records, with expressions and immutable collections make such functions practical in C#.

  1. Mid to senior

    The cart page shows Rs 679 for an order and the invoice, priced a moment later, shows Rs 656.10. Both call ApplyOffers(cart) on the same cart object. How do you work out why, and how do you redesign the pricing so that it cannot happen again?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A sign-up API throws ValidationException for every bad field. During a wave of bot sign-ups it handles 5,000 invalid requests a second and its CPU use and garbage collections climb. Would you switch to a Result type, and what changes for the callers and for the cost?

    Model answer, Pro. First step and follow-ups

  3. Senior to staff

    A 3,000-line OrderService reads the clock, the database and configuration inside its pricing and shipping rules, and has almost no tests. How would you move it toward a functional core with an imperative shell, step by step, without stopping feature work?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A pure function changes nothing outside itself and gives the same result for the same arguments, so it is easy to test and safe to run in parallel.
  • Records, with and immutable collections change data by making new values, so old values stay valid.
  • Functions are values: pass them, return them with some arguments fixed, and join them with Then into a pipeline whose order is visible.

Terms in this lesson

function composition
Joining two functions into one, so that the result of the first becomes the argument of the second: f then g is the function x => g(f(x)).
higher-order function
A function that takes another function as an argument or returns one, such as a method with a Func parameter, or a method that returns a lambda.
partial application
Fixing some of a function's arguments now and the rest later. In C# a method returns a lambda that remembers the fixed arguments in a closure, as PercentOff(10) returns a rule that waits for an amount.
pure function
A function that has no side effects and always returns the same result for the same arguments, because it depends on nothing but them: no clock, no shared field, no input it reads by itself.
side effect
A change a function makes outside itself while it runs: writing a field, changing an argument, printing, saving a file. A function with side effects can give another result the next time it is called.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.