Fsharp

De Banane Atomic
Aller à la navigationAller à la recherche

Links

Command line

Ps.svg
dotnet fsi  # start F# Interactive
dotnet fsi script.fsx  # run script.fsx

dotnet new console -o fsharp1 -lang f#

printf

Fs.svg
let s = "value"
printfn "text %A text" s
Format Type
%A any
%s string
%i integer
%b boolean

for

Fs.svg
for i in [1..3] do
    printfn "%A" i

for i = 1 to 3 do
    printfn "%A" i

while

Fs.svg
let mutable keepRunning = true
while keepRunning do
    // ...
    keepRunning <- false

match

Fs.svg
let x = 2
let result =
    match x with
    | 1 -> "one"
    | 2 -> "two"
    | 3 | 4 -> "soon implemented" // 3 or 4
    | _ -> "not implemented"
    | y -> $"not implemented for {y}"

// pattern matching function syntax
let numberToWord = function
    | 1 -> "one"
    | 2 -> "two"
    | _ -> "not implemented"
numberToWord x  // "two"

String

Fs.svg
let s = "abcde"

// slice
s[1..3]  // "bcd"

// triple-quote string allows to use double-quote within the string
let s = """<tag attribute="value" />"""

// concat strings
let sb = StringBuilder()
sb.Append("abc") |> ignore
sb.Append("def") |> ignore

Collection

List

Ordered, immutable, series of elements of the same type.

Fs.svg
let list1 = [1; 2; 3]

let list2 = [1..5]      // [1; 2; 3; 4; 5]
let list3 = [0..2..10]  // [0; 2; 4; 6; 8; 10]

let list4 = [for x in 1..5 -> x*x] // [1; 4; 9; 16; 25]

// elements in lists are not mutable

// cons operator
0 :: list1  // create a new list with 0 at the head [0; 1; 2; 3]
// extract head and tail
let head :: tail = list1  // head: 1, tail: [2; 3]

// concatenate operator
list1 @ [4; 5]  // create a new list [1; 2; 3; 4; 5]

List.sum(list1)  // 6
List.fold (+) 0 list1  // 6 - aggregate
List.iter (fun x -> printfn "%A" x) list1  // 1 2 3

Array

Fixed-sized, zero-based, mutable collection of consecutive elements of the same type.

Fs.svg
let myArray = [|1; 2; 3|]

// elements in arrays are mutable
myArray[1] <- 4  // [|1; 4; 3|]

Map

Set

Immutable Dictionary

Composite data type

Tuple

Fs.svg
let tuple1 = (1, "one")
let (a, b) = tuple1  // a: 1, b: "one"

Record

Fs.svg
type Item  = { Id: int; Name: string; }
let item1 = { Id = 1; Name = "one" }

Function

Fs.svg
let add x y = x + y
add 1 2  // 3

let myFunction param1 param2 =
    let result = param1 + param2
    result

// let myFunction (param1: int) (param2: int) : int = ...

myFunction 1 2  // 3

Unit

Function which doesn't return any value.

Fs.svg
let myUnit (param1: int) (param2: int) =
    let result = param1 + param2
    printfn "%i" result
    ()  // useless because printfn is already a unit

myUnit 1 2  // 3

Convertion

Fs.svg
int "123"   // 123
float 123   // 123.0
string 123  // "123"

Console input

Fs.svg
printf "Enter a value: "
let input = Console.ReadLine()
printfn "Entered value: %s" input
.vscode/launch.json
{
    "configurations": [
        {
            "console": "integratedTerminal" // // switch from internalConsole to integratedTerminal to allow console input in VS code
        }
    ]
}

Use binding for disposable object

Fs.svg
use file = File.CreateToText("file.txt")
// Dispose will be called when binding goes out of the scope

Module

Équivalent to a C# static class.

Fs.svg
// nested module
module Math =
    let add x y = x + y
    let sub x y = x - y

let v = Math.add 1 2
open Math
let v = add 1 2

// top level module (no =, no indentation, in a separate file)
module Math
let add x y = x + y
let sub x y = x - y

Module Design Pattern

Separate the definition of data and the behaviors.

DomainTypes.fs
namespace Finance

module DomainTypes =
    type Transaction =
        { Id: int; Amount: float}
Transaction.fs
namespace Finance

module Transaction =
    let create id amount =
        { Id = id; Amount = amount }

Execute a shell command

Process.fs
module Process =
    let Run command args =
        let psi = Diagnostics.ProcessStartInfo()
        psi.UseShellExecute <- false
        psi.WindowStyle <- Diagnostics.ProcessWindowStyle.Hidden
        psi.RedirectStandardOutput <- true
        psi.RedirectStandardError <- true

        let stdout = Event<string>()
        let stderr = Event<string>()

        psi.FileName <- command
        psi.Arguments <- args
        // psi.WorkingDirectory

        let stdout = Event<string>()
        let stderr = Event<string>()

        let writeOut = printfn "%s"
        stdout.Publish |> Event.add writeOut
        stderr.Publish |> Event.add writeOut

        use p = new Diagnostics.Process()
        p.StartInfo <- psi
        p.OutputDataReceived |> Event.add(fun ev -> if ev.Data <> null then stdout.Trigger ev.Data)
        p.ErrorDataReceived.Add(fun ev -> if ev.Data <> null then stderr.Trigger ev.Data)
        p.Start() |> ignore

        p.BeginOutputReadLine()
        p.BeginErrorReadLine()

        p.WaitForExit()

        p.ExitCode

Compilation order

MyProject.fsproj
<ItemGroup>
    <Compile Include="File2.fs" />
    <Compile Include="File1.fs" />
</ItemGroup>

Dev env

  • VScode
    • Ionide for F# extension → intellisense
    • fantomas-fmt → format F#

Create launch and tasks

  1. Disable the Ionide for F# extension
  2. Run → Run Without Debugging
  3. Select environment: .NET 5+ and .NET Core (this create the launch.json file)
  4. Open launch.json → Add Configuration → .NET: Launch .NET Core Console App
  5. Fill target-framework = net6.0 and project-name = myproject
  6. Run → Run Without Debugging
  7. Configure Task → Create tasks.json file from template → Select a Task Template: .NET Core