F# — basics
F# next to C# is one of the .NET programming languages. There are a lot of materials on the Internet on how you can learn it. This post is a concise cheat sheet that provides you with a soft start during explorations.
A link to a GitHub repository is placed at the end of the article. A file is an .fsx file type. You can run it by a dotnet fsi file_name.fsx command.
Like in every language, we need to get to know how to make variables. Variables are immutable by default. Firstly, if you want to create a mutable variable you need to do it explicitly. Secondly, variables don’t have to have a type assigned — it is optional.
// An immutable variable
let variable00 = 50 // a type not defined
let (variable01: int) = 100 // a type defined
// A mutable variable
let mutable variable02 = "This is a string" // a type not defined
let mutable (variable03:int) = 100 // a type defined
variable02 <- "A string second version" // assign a new value
printfn "%d" variable00
// 50
printfn $"{variable01}"
// 100
printfn "%s" variable02
// A string second version
Here are examples of collections, like: lists, tuples, arrays, sequences.
// A list
let list00 = [ 5; 6; 8 ]
let list01 = [ 20..30 ]
let list02 = List.init<int> 10 (fun i -> i)
let list03 = list01 |> List.append list00
printfn "%A" list03
// [5; 6; 8; 20; 21; 22; 23; 24; 25; 26; 27; 28; 29; 30]
printfn "%d" (list00.Item(1))
// 6
printfn "%d" (list00.Length)
// 3
// A sequence
let sequence00 = { 1..50 } // increment +1
let sequence01 = {1..10..101} // increment +10
let sequence02 = Seq.init<int> 20 (fun i -> i)
// A tuple
let tuple00 = (2, 3, 55)
// An array
let array00 = [| 1; 4; 5 |]
let array01 = Array.init<int> 10 (fun i -> i)
let array02 = Array2D.init<int> 5 5 (fun a b -> a + 1)
And more complicated structures, like: records, discriminated unions, lists of records.
//Discriminated union
type ProjectState =
| Active
| Closed
type CompanyProject = // this is a normal record, but ProjectState is enforced by Discriminated union
{
Name:string
Type:int
State:ProjectState
}
let (firstProject:CompanyProject) =
{
Name = "Project01"
Type = 5
State = Active
}
printfn $"{firstProject.State}"
// Active
// A list of records
type Transaction = {
Date:DateTime;
CustomerId:int;
Amount:double
}
let mutable transactions00 = [
{
Date = new DateTime(2023, 12, 01);
CustomerId = 10;
Amount = 10.5
};
{
Date = new DateTime(2023, 12, 02);
CustomerId = 11;
Amount = 11.5
};
{
Date = new DateTime(2023, 4, 4);
CustomerId = 14;
Amount = 11.4
}
]
printfn $"{transactions00.Head}" // a first record
printfn $"{transactions00.Tail}" // a last record
printfn $"{transactions00.[0]}" // a record with index 0
// look for particular records, you can use List.tryFind as well
let tF = transactions00 |> List.find (fun transaction -> transaction.CustomerId = 10)
printfn $"{tF.Amount}"
// 10.5
// add a record to a transaction records list
transactions00 <- [{Date = new DateTime(2024, 05,05);CustomerId = 15; Amount = 12.5}] |> List.append transactions00
You can use a pipeline if you want to chain more operations in one command. Here are a few examples, based on the transactions list.
// convert the list to an array
let transactionsArray = transactions00 |> Array.ofList
// modify an Amount value for every record
let multipler = 2.0
let transactions01 = transactions00 |> List.map (fun tr -> {| Amount = tr.Amount * multipler|})
// print all the records
transactions00 |> List.iter(fun tr -> printfn $"{tr.Amount}")
// aggregate, sum in this case,
transactions00 |> List.sumBy(fun tr -> tr.Amount)
//filter and sort
transactions00 |> List.filter(fun tr -> tr.Date = DateTime(2023, 4,4)) |> List.sortBy(fun tr -> tr.CustomerId)
As you see, the pipeline is marked with |> command. A previous element is an argument for a next command. Here are more complicated examples along with a composition operator with is pretty similar to the pipeline.
// Pipelines
let seqence00 = seq{10..20}
let finalSeq =
seqence00
|> Seq.filter(fun c -> (c%2)=0)
|> Seq.map((*) 2)
|> Seq.map(sprintf"The value is %i")
printfn "%A" finalSeq
// seq["The value is 20"; "The value is 24"; "The value is 28"; "The value is 32";...]
let finalFunc =
2
|> addValues00 <| 3
|> nextValue00 <| 20
|> printfn "%d"
// Composition
let addValues03 (valueA:int):int = valueA*2
let nextValue01 (valueA:int):int = (valueA + 1)*2
let finalFunc01 = addValues03 >> nextValue01 >> printfn "%d"
finalFunc01 5
// 25
In the above example custom functions ware implemented. Here are another example.
// Functions
let addValues00 valueA valueB = valueA + valueB
let nextValue00 (valueA:int) (valueB:int):int = valueA + valueB + 1
let nextValueCalc = nextValue00 1 6
let addValues01 = fun valueA valueB -> valueA + valueB
printfn $"Function: {addValues00 5 15}, {addValues01 5 5}"
// "Function: 20, 10"
And a class.
// Clases
type Customer(firstName:string, lastName:string, age:int) =
//properties
member this.FirstName = firstName //immutable
member this.LastName = lastName
member val Age = age with get,set //mutable
//method
member this.ageAdd =
this.Age <- this.Age + 5
let firstCustomer = Customer("John", "Doe", 45) // an object initialization
printfn $"{firstCustomer.Age}"
// 45
firstCustomer.ageAdd
printfn $"{firstCustomer.Age}"
// 50
It is time to take a step back and look at loops and conditions.
// A for loop
for i=3 to 20 do
printfn "%d" i
for i=20 downto 1 do
printfn "%d" i
let listA = [1..5]
let listB = [for t in listA do t + 5]
// A for each loop
listB |> List.iter(fun tr -> printfn "%d" tr)
for item in listB do
printfn "%d" item
// A while loop
let mutable input = ""
while (input <> "q") do
input <- Console.ReadLine()
printfn "%s" input
// An if condition
if 2=2 then
printfn "%d" 1
elif 2=3 then
printfn "%d" 2
else
printfn "%d" 3
// Pattern matching - switch
let matchFunction x =
match x with
| 1 -> printfn "%s" "is 1"
| 2 -> printfn "%s" "is 2"
|i when i >2 && i <5 -> printfn "< 5 > 2"
| _ -> printfn "%s" "is more than 5"
matchFunction 6
// is more than 5
And other functionalities like try/catch, defining custom modules.
// Try catch
let someFunction x y =
try
x/y
with
| :? System.DivideByZeroException -> printfn "Division custom exception";0
| ex -> printfn "Some other err";0
someFunction 5 0
// Division custom exception
// Modules
module addFunctionModule =
let addFunction x y =
x + y
open addFunctionModule
addFunction 5 5
The last thing is an asynchronous way of working.
// Asynchronous approach
let asyncFunction x =
async {
let a = x * 5
return a
}
5 |> asyncFunction |> Async.RunSynchronously |> printfn "%d"
let list05 = [3;4;5]
list05
|> List.map asyncFunction
|> Async.Parallel
|> Async.RunSynchronously
|> printfn "%A"
list05
|> List.map asyncFunction
|> Async.Sequential
|> Async.RunSynchronously
|> printfn "%A"
Here is the GitHub file location.