F# Training
F# Training
F# Training
  • Presentation
  • Fundamentals
    • Introduction
    • Syntax
      • Bases
      • Functions
      • Rules
      • Exceptions
    • First concepts
    • šŸ”Quiz
  • Functions
    • Signature
    • Concepts
    • Syntax
    • Standard functions
    • Operators
    • Addendum
    • šŸ”Quiz
    • šŸ“œSummary
  • Types
    • Overview
    • Tuples
    • Records
    • Unions
    • Enums
    • Anonymous records
    • Value types
    • šŸ“œRecap
    • Addendum
  • Monadic types
    • Intro
    • Option type
    • Result type
    • Smart constructor
    • šŸš€Computation expression (CE)
    • šŸš€CE theoretical basements
    • šŸ“œRecap
  • Pattern matching
    • Patterns
    • Match expression
    • Active patterns
    • šŸš€Fold function
    • šŸ“œRecap
    • šŸ•¹ļøExercises
  • Collections
    • Overview
    • Types
    • Common functions
    • Dedicated functions
    • šŸ”Quiz
    • šŸ“œRecap
  • Asynchronous programming
    • Asynchronous workflow
    • Interop with .NET TPL
    • šŸ“œRecap
  • Module and Namespace
    • Overview
    • Namespace
    • Module
    • šŸ”Quiz
    • šŸ“œRecap
  • Object-oriented
    • Introduction
    • Members
    • Type extensions
    • Class, Struct
    • Interface
    • Object expression
    • Recommendations
Powered by GitBook
On this page
  • From void to unit
  • Issues with void in C♯
  • Exemple : ITelemetry
  • Type Void
  • ITelemetry simplification
  • Type unit
  • Impact on the function signature
  • ignore function
  • Arrow notation
  • ā“ Quiz

Was this helpful?

Edit on GitHub
  1. Functions

Signature

PreviousQuizNextConcepts

Last updated 2 months ago

Was this helpful?

From void to unit

Issues with void in C♯

void needs to be handle separately = 2 times more work 😠

  • 2 types of delegates: Action vs Func<T>

  • 2 types od task: Task vs Task<T>

Exemple : ITelemetry

interface ITelemetry
{
  void Run(Action action);
  T Run<T>(Func<T> func);

  Task RunAsync(Func<Task> asyncAction);
  Task<T> RunAsync<T>(Func<Task<T>> asyncFunc);
}

šŸ”— Mads Torgersen is the C#'s Lead Designer. In , at 1:21:25, he indicates the 3 features he would completely remove from C#: event, delegate and void!

Type Void

The issue with the void is that it's neither a type nor a value.

šŸ’” What about creating a Void type, a singleton with no data in it:

public class Void
{
    public static readonly Void Instance = new Void();

    private Void() {}
}

Let's play with it...

ITelemetry simplification

First, let's define the following helpers to convert to Void :

public static class VoidExtensions
{
    // Action -> Func<Void>
    public static Func<Void> AsFunc(this Action action)
    {
        action();
        return Void.Instance;
    }

    // Func<Task> -> Func<Task<Void>>
    public async static Func<Task<Void>> AsAsyncFunc(this Func<Task> asyncAction)
    {
        await asyncAction();
        return Void.Instance;
    }
}

Then, we can write a default implementation (C♯ 8) for 2 of 4 methods:

interface ITelemetry
{
    void Run(Action action) =>
        Run(action.AsFunc());

    T Run<T>(Func<T> func);

    Task RunAsync(Func<Task> asyncAction) =>
        RunAsync(asyncAction.AsAsyncFunc());

    Task<T> RunAsync<T>(Func<Task<T>> asyncFunc);
}

Type unit

In F♯ theVoid type exists! It's called unit because it has only one instance, written() and that we use like any other literal.

Impact on the function signature

  • Rather than void functions, we have functions returning the unit type.

  • Rather than functions with 0 parameter, we have functions taking a unit parameter that can only be ().

let doNothing () = () // unit -> unit
let now () = System.DateTime.Now // unit -> DateTime

ignore function

Remember : in F♯, everything is an expression. → No value is ignored, except ()/unit designed for this purpose → At the beginning of an expression or between several let bindings, we can insert unit expressions worth ()/unit, for example printf "mon message"

Issue: we call a function which triggers a side-effect but also returns a value we are not interested in.

Example: save is a function that saves to the database and returns true or false

let save entity = true // Fake implementation

let problem =
    save "hello"
//  ~~~~~~~~~~~~ āš ļø
// Warning FS0020: The result of this expression has type 'bool' and is implicitly ignored.
// Consider using 'ignore' to discard this value explicitly, e.g. 'expr |> ignore',
// or 'let' to bind the result to a name, e.g. 'let result = expr'.

    "ok" // Random final value just for demo purpose

Solution 1 : discard the returned value

let solution1 =
    let _ = save "hello" // šŸ‘Œ
    "ok"

Solution 2 : use the built-in ignore function, that has this signature :'a -> unit → Whatever the value supplied as parameter, it ignores it and returns ().

let solution2 =
    save "hello" |> ignore // šŸ‘
    "ok"

Other examples:

// Side-effects / file system
System.IO.Directory.CreateDirectory folder |> ignore
// Ignore the returned DirectoryInfo

// Fluent builder:
let configureAppConfigurationForEnvironment (env: Environment) (basePath: string) (builder: IConfigurationBuilder) =
    builder
        .SetBasePath(basePath)
        .AddJsonFile("appsettings.json", optional = false, reloadOnChange = true)
        .AddJsonFile($"appsettings.{env.name}.json", optional = false, reloadOnChange = true)
    |> ignore

// Event handler:
textbox.onValueChanged(ignore)
[ 1..5 ]
|> Seq.map save
|> ignore // šŸ’£

// Expected
[ 1..5 ]
|> Seq.iter (save >> ignore)

Arrow notation

  • 0-parameters function: unit -> TResult.

  • 1-parameter function: T -> TResult.

  • 2-parameters function: T1 -> T2 -> TResult * 3-parameter function: `T1 -> T2 -> T3 -> TResult

  • 3-parameters function: T1 -> T2 -> T3 -> TResult

ā“ Quiz

Do you know why we have -> between the parameters? What is the underlying concept?

Answer in the next page...

Trap: ignoring a value that we should use in our program.

āš ļø
his interview by Nick Chapsas