stolnikov / pragmatic-fsharp

Explore functional programming with F# in the form of questions and answers

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Pragmatic FSharp

Pragmatic guide to F#

Explore functional programming with F#

⚠️ This is a work in constant progress


📙 source code link

📝 author's personal opinion on controversial topics

Some goals while writing this book:

  • try to present the material in a clear and coherent manner
  • provide:
    • examples when necessary
    • references to useful resources and terminology to save a reader some googling time
    • links to source code repositories

Table of contents

Introduction

What is F#?

F# is a functional-first, strongly typed, multi-paradigm, general-purpose programming language that can be used to develop software in many different application domains.

  • Functional-first: Today, simply referring to a programming language as "functional" does not adequately convey what it actually represents because most modern mainstream languages are functional in part. F# offers a powerful type system and concise syntax which encourage a developer to write code in a functional way while also supporting object-oriented and imperative programming styles and thus being a hybrid language.

Is F# a purely functional language?

Even though F# is heavily geared towards functional paradigm, it's not a purely functional language because it provides a fair number of escape hatches, i.e.:

  • Using reference cells circumvents value and function bindings.

  • An identifier that is bound to a value with mutable keyword can then be reassigned with <- operator.

  • F# supports procedural loops which is an imperative concept. In a purely functional language, such a construct makes no sense because loops are only useful in combination with mutable state.

    📙 List.fold function uses a "for loop" with a mutable variable to store intermediary results. In a purely functional language, this can be implemented using a tail-recursive function (left fold) where each consecutive iteration gets the intermediary result as an explicit function argument thus eliminating the need for a mutable acc variable.

[<CompiledName("Fold")>]
let fold<'T,'State> folder (state:'State) (list: 'T list) = 
    match list with 
    | [] -> state
    | _ -> 
        let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(folder)
        let mutable acc = state
        for x in list do
            acc <- f.Invoke(acc, x)
        acc

⬆ Back to Top

Working with functions

Basic terminology

What formal system lies at the core of almost all functional programming?

Functional programming has its roots in academia, evolving from the lambda calculus, a formal system of computation based only on functions.

Watch about Lambda Calculus on Computerphile

Lambda Calculus - Computerphile

https://www.youtube.com/watch?v=eis11j_iGMs

What is arity?

Arity is the number of arguments or operands taken by a function or operation in logic, mathematics, and computer science.

What is currying? What is partial application?

Currying is a technique of transforming a function that has more than one parameter into a chain of nested functions, each of which takes a single parameter.

Partial application is a process of fixing a number of arguments to a function that produces another function of smaller arity bigger than one. It's an irreversible operation; you can't unapply arguments.

What is function composition?

In mathematics, function composition is an operation that takes two functions f and g and produces a function h such that h(x) =g(f(x)).

What is an expression in computer science?

In computer science, an expression is a syntactic entity in a programming language that may be evaluated to determine its value. It is a combination of one or more constants, variables, functions, and operators that the programming language interprets (according to its particular rules of precedence and of association) and computes to produce ("to return", in a stateful environment) another value. This process, for mathematical expressions, is called evaluation.

What is purity and referential transparency as applied to expressions?

Referential transparency is a property of expressions such that they can be replaced with their output without affecting a program's behavior.

An expression is pure when it is deterministic – it evaluates to the same value when provided with the same input, and its evaluation produces no side effects. In addition, a function should not rely on any mutable data outside its scope (implicit arguments) that might change between calls, otherwise it will break its referential transparency. Referential transparency of a pure expression is the logical corollary of it being pure.

📝 In the context of F#, which is an impure language, an impure expression can arguably have the property of referential transparency when it includes calls to impure functions and its resulting value does not depend on their results (if any) and side effects. For instance, consider a function with a side effect of logging its computation progress to the standard output. You can replace it with a similar pure function that does not log anything and produces the same results, which in effect means it is referentially transparent.

What are the reasons for using mutation-based function implementations in FSharp.Core?

For obvious reasons, library functions like 📙 Array.contains are performance-critical:

[<CompiledName("Contains")>]
let inline contains value (array:'T[]) =
    checkNonNull "array" array
    let mutable state = false
    let mutable i = 0
    while not state && i < array.Length do
        state <- value = array.[i]
        i <- i + 1
    state

There are plenty of algorithms that can be made more performant or/and memory efficient if implemented on top of mutation. It is a good practice to wrap mutable code in immutable interfaces to achieve referential transparency and to not expose mutable state to the consuming code so that the caller isn't required to maintain it.

⬆ Back to Top

Operators

What are >> and << operators?

These are function composition operators (📙 source) which are used to combine two functions into one.

Forward composition operator >> reads as: given two functions, f and g, and a value, x, compute the result of f of x and pass that result to g.

// forward composition operator
// val ( >> ) : f: ('a -> 'b) -> g: ('b -> 'c) -> x: 'a -> 'c
//              ~~~~~~~~~~~~~    ~~~~~~~~~~~~~    ~~~~~    ~~~
//                    ^                ^            ^       ^
//                    |                |            |       |
//                    |                |            |       |
//              1st parameter    2nd parameter  3rd param   /
//                                                         |
//                                                       result
let inline (>>) f g x = g (f x)
(f >> g) x = x |> (f >> g) = x |> f |> g = g (f x)

Backward composition operator << composes two functions in reverse order; the second one is executed first.

// backward composition operator
// val ( << ): g: 'a -> 'b -> f: 'c -> 'a -> x: 'c -> 'b
let inline (<<) g f x = g (f x)
(g << f) x = g (f x)

⬆ Back to Top

Type System

What is the difference between variables in F# and C#?

In contrast to imperative programming languages, functional languages emphasize the use of immutable values over mutable variables. In F#, values are immutable by default and there is a clear distinction between the concepts of assigning a value to a variable (<-) and binding a value to an identifier (=). A variable can only be reassigned when marked as mutable at its declaration.

What are units of measure in F#? Is there an extra runtime overhead?

Units of measure are a type of metadata that can be associated with floating point or signed integer values. This metadata is then used by the compiler to check whether arithmetic relationships of annotated values are valid. It gets erased during compilation and therefore does not incur a performance hit at runtime.

How can F# assist us in "making illegal states unrepresentable" when designing our domain model?

With F# type system, we can encode simple domain rules directly into types.

⬆ Back to Top

Asynchronous programming

What is Async<'T> type in F#? What is async?

Async<'T> type is the core concept of F# asynchronous programming and represents a composable asynchronous computation. A value of type Async<_> is best thought of as a “task specification” or “task generator” (📙 source).

/// Represents an asynchronous computation
[<NoEquality; NoComparison; CompiledName("FSharpAsync`1")>]
type Async<'T> =
    { Invoke : (AsyncActivation<'T> -> AsyncReturn) }

async is not a keyword in F# as it may seem at first glance. Rather, it is an identifier, an alias for AsyncBuilder() object (📙 source).

[<CompiledName("DefaultAsyncBuilder")>]
let async = AsyncBuilder()

The async { } computation expression provides a convenient syntax for building and controlling asynchronous computations. The expression placed within the async block (async { expression }) is set up to run asynchronously. The async { } computation expression itself is of type Async<'T>. It will produce a value of type 'T, when executed, and deliver it to continuation.

What are the differences between Task<T> in C# and Async<'T> in F#?

In C# asynchronous methods return tasks of type Task<T> (or Task if a method returns void) that run immediately after the method is called and eventually produce a value. Consider this top-level C# 9 console application.

using System;
using System.Threading;
using System.Threading.Tasks;

static void ShowThreadInfo()
{
    var currentThread = Thread.CurrentThread;
    Console.WriteLine(
        "Thread ID: {0}, IsThreadPoolThead: {1}", 
        currentThread.ManagedThreadId, 
        currentThread.IsThreadPoolThread);
}

async Task AsyncTest()
{
    Console.WriteLine("start");
    await Task.Run(ShowThreadInfo);
    Console.WriteLine("end");
}

AsyncTest();
Output

$ dotnet run
warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
start
Thread ID: 4, IsThreadPoolThead: True
end

Now let's take a look at the equivalent code in F# with the only difference that Async.Start is not awaited by design.

open System.Threading
let showThreadInfo =
    async {
        let currentThread = Thread.CurrentThread
        System.Console.WriteLine
            ("Thread ID: {0}, IsThreadPoolThead: {1}",
             currentThread.ManagedThreadId,
             currentThread.IsThreadPoolThread)
    }

let asyncTest =
    async {
        printfn "start"
        showThreadInfo |> Async.Start
        printfn "end"
    }

[<EntryPoint>]
let main argv =
    asyncTest
    0

In this case, there will be no output. Calling the asyncTest function doesn't start a task until you provide a continuation function to be called when the operations within the async { } block complete:

[<EntryPoint>]
let main argv =
   asyncTest |> Async.RunSynchronously
   0
Output

$ dotnet run
start
end
Thread ID: 4, IsThreadPoolThead: True

⬆ Back to Top

Web Development: Backend

What is >=> operator in Giraffe?

Web Development: Frontend

About

Explore functional programming with F# in the form of questions and answers


Languages

Language:F# 100.0%