Category Archives: F#

Exploring F# with .NET and Docker on Raspberry Pi

Recently, I got a Raspberry Pi 3 Model B. Now, I’d like to figure out, if I can run an F# server in a Docker container on it. I am aware that the currently installed Raspberry Pi OS is actually a 32-bit OS while the processor of my Pi has an 64-bit architecture. This has an impact if I’d like to install .NET directly on the OS. Since I intend to use Docker, I’ll sort that out later and just try to get it going.

Proof of principle

Try to run a container with .NET installed:

sudo docker run -it microsoft/dotnet:latest

Since I managed to spin up a .NET container, the next step is to create an F# console application and run it:

mkdir test
cd test
dotnet new console -lang "F#"
dotnet run

I am also able to run the dll file directy:

dotnet bin/Debug/netcoreapp2.1/test.dll 

To exit the container and keep it running, I press [ctrl] + [p] followed by [ctrl] + [q].

Bind mount a host directory

First I create in the current directory a folder hostFolder containing a subfolder on the host system. I’ll mount hostFolder into the container below.

mkdir hostFolder
mkdir hostFolder/subfolder

Then I create a container from the image microsoft/dotnet:latest. Using the -v flag, I mount the hostFolder into the /targetFolder in the container. Note that the directory targetFolder is created in the root directory (/). The term "${PWD}"/hostFolder determines the path of the hostFolder to mount with ${PWD} being the current working directory. The part after the colon : with the path /targetFolder defines the mount point in the container. The -w flag determines the working directory in the container. Thus, I get directly into the /targetFolder/subFolder directory in the container.

sudo docker run -it \
       -v "${PWD}"/hostFolder:/targetFolder \
       -w /targetFolder/subfolder \
       microsoft/dotnet:latest

For testing I create a test file within the subfolder on the container and see it on the host system.

sudo docker run -it -v "${PWD}"/Source/Repos/Lab/TestApp:/TestApp  -w /TestApp microsoft/dotnet:latest

Add a Dockerfile

A Dockerfile describes how Docker creates an image and runs it. However, I’d like to develop on my Mac and try to setup the docker image there. Thus, I’ll setup a SAFE stack app on my Mac in another post.

References

Define the assembly information of an F# .NET Core app

There are several options to define the assembly information of an .NET Core app in F#:

  • .fsproj file
  • Directory.Build.props file
  • When running dotnet build or dotnet publish: -p:Version=a.b.c.d

The information of the .fsproj file seems to have priority over Directory.Build.props file. You can place the Directory.Build.props file in the project folder or the solution folder.

.fsproj file

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>

    <Version>1.0.0.0</Version>
    <FileVersion>1.0.0.0</FileVersion>
    <Product>MyApp</Product>
    <Copyright>Copyright © 2020 by myself</Copyright>
  </PropertyGroup>

  <ItemGroup>
  ...

Directory.Build.props file

<Project>
  <PropertyGroup>
    <Version>1.0.0.0</Version>
    <FileVersion>1.0.0.0</FileVersion>
    <Product>MyApp</Product>
    <Copyright>Copyright © 2020 by myself</Copyright>
  </PropertyGroup>
</Project>

Check the assembly version

The simple way

  • Run: cat MyApp.dll
  • Check the last lines. They contain the assembly information

The convenient way

Install the versioninfo tool and run it on the .dll:

  • dotnet tool install --global dotnet-versioninfo
  • versioninfo MyApp.dll

References

Debug an F# .NET Core app in VS Code on macOS

Press ‘F5’

The F5 button is the magic button to start debugging. However, if you start debugging the first time, you need to set up VisualStudio Code first. You need to setup a launch.json file to start debugging and a tasks.json file to first run the process.

Setup

  • Press F5
  • Select .NET Core
Initial setup for debugging: Press 'F5' and select '.NET Core'
Initial setup for debugging: Press ‘F5’ and select ‘.NET Core’
  • VS Code generates the launch.json file in the .vscode folder and opens it.
VS Code generates 'launch.json' in the '.vscode' folder
VS Code generates ‘launch.json’ in the ‘.vscode’ folder
  • Edit the launch.json file
  • Insert the configuration entry within the selected square brackets of the ‘configuration’ section
    • Press Ctrl + Space Bar
    • Select .NET: Launch .NET Core Console App
    • Enter the path to the application .dll file in the program attribute:
      "${workspaceFolder}/src/MailBoxTransformer/bin/Debug/netcoreapp3.1/MailBoxTransformer.dll"
    • The path differs from the original template because the project is within the src sub-folder.
Insert configuration entry by pressing 'Ctrl' + 'Space Bar' '.NET: Launch .NET Core Console App'
Insert configuration entry by pressing ‘Ctrl’ + ‘Space Bar’ ‘.NET: Launch .NET Core Console App’
Enter the path of the application '.dll' file in the 'program' attribute
Enter the path of the application ‘.dll’ file in the ‘program’ attribute
  • Press F5 again
  • VS Code now complains that it does not find the build task defined in the preLaunchTask attribute of the launch.json file
  • Select Configure Task
VS Code does not find the 'preLaunchTask' called 'build' and suggests to configure the task.
VS Code does not find the ‘preLaunchTask’ called ‘build’ and suggests to configure the task.
  • Select Create tasks.json file from template
Configure Task: Create 'tasks.json'
Configure Task: Create ‘tasks.json’
  • Choose the .NET Core Executes .NET core build command template
Select Task Template '.NET Core Executes .NET Core build command'
Select Task Template ‘.NET Core Executes .NET Core build command’
  • VS Code generates the tasks.json file in the .vscode folder and opens it.
  • Edit the tasks.json file
  • If the .fsproj file is not in the main folder
  • Insert the path of the directory containing the .fsproj file as an additional argument for dotnet build.
    • "${workspaceFolder}/src/MailBoxTransformer",
  • To see the logs of the build task in the console (optional):
    • Change the presentation attribute in the tasks.json file
      from "silent" to "always"
  • Press F5 again
  • VS Code runs
    • The dotnet build process
    • The app
  • Add the launch.json and the tasks.json files located in the .vscode folder to your source control system (e.g. git).
Console output of the app
Console output of the app

You are now able to start the build process and the app itself from within VS Code. Now you can set break points and debug your app. I have included the complete launch.json and the tasks.json files below. Have fun!

launch.json

  • Located in the .vscode folder
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": ".NET Core Launch (console)",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            "program": "${workspaceFolder}/src/MailBoxTransformer/bin/Debug/netcoreapp3.1/MailBoxTransformer.dll",
            "args": [],
            "cwd": "${workspaceFolder}",
            "stopAtEntry": false,
            "console": "internalConsole"
        }
    ]
}

tasks.json

  • Located in the .vscode folder
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "command": "dotnet",
            "type": "shell",
            "args": [
                "build",
                "${workspaceFolder}/src/MailBoxTransformer",
                // Ask dotnet build to generate full paths for file names.
                "/property:GenerateFullPaths=true",
                // Do not generate summary otherwise it leads to duplicate errors in Problems panel
                "/consoleloggerparameters:NoSummary"
            ],
            "group": "build",
            "presentation": {
                "reveal": "always"
            },
            "problemMatcher": "$msCompile"
        }
    ]
}

References

Create an F# .NET Core app on macOS

Create a console app with the CLI

dotnet new console -lang "F#" -o src/MyConsoleApp

Run the app in the terminal

The easy way: Move first to the folder with the .fsproj file and then use dotnet run without any arguments:

cd src/MyConsoleApp
dotnet run

If you want to start the app from a location different than the one containing the .fsproj file, you need to enter the argument --project with the path to the .fsproj file.

dotnet run --project src/MyConsoleApp/MyConsoleApp.fsproj 

References

Coding F# on macOS

Lately, I wanted to code some F# scripts. I started up Visual Studio Code on my MacBook and created an fsx file. Then I noticed that intellisense was not working anymore despite active code highlighting. That was the start for a long journey. I ended up on installing (again):

Intellisense was now working in F# projects and somehow/sometimes in fsx files within the context of an F# project. Then I tried it out in a stand-alone fsx file. Intellisense was not working again… Then I found some notes about the ‘FSharp.useSdkScripts’ option of the ionide-fsharp extension of Visual Studio Code. I activated the setting:

Visual Studio Code settings: Activate “Fsharp: Use Sdk Scripts: Use ‘dotnet fsi’ instead of ‘fsi.exe’/’fsharpi'”.

What a wonder, intellisense is working for F# fsx script files! With this setting enabled, I am no longer working with mono but with the .NET Core version of fsi (FSharp Interactive). With that, I think I don’t actually need mono.

References

F# DynamicOracleDataReader

Dynamic reading from an Oracle database

Implementation

let inline tryUnbox<'a> (x:obj) =
    match x with
    | :? 'a as result -> Some(result)
    | _ -> None

type DynamicOracleDataReader(reader:OracleDataReader) =
    member private x.Reader = reader
    member x.Read() = reader.Read()
    static member (?) (dr:DynamicOracleDataReader, name:string) : 'a option = 
        tryUnbox (dr.Reader.[name])
    interface IDisposable with
        member x.Dispose() = reader.Dispose()

type DynamicOracleCommand(cmd:OracleCommand) =
    member private x.Command = cmd
    static member (?<-) (cmd:DynamicOracleCommand, name:string, value) = 
        let p = new OracleParameter(name, box value) 
        cmd.Command.Parameters.Add(p) |> ignore
    member x.ExecuteNonQuery() = cmd.ExecuteNonQuery()
    member x.ExecuteReader() = new DynamicOracleDataReader(cmd.ExecuteReader())
    member x.ExecuteScalar() = cmd.ExecuteScalar()
    member x.Parameters = cmd.Parameters
    interface IDisposable with
        member x.Dispose() = cmd.Dispose()

type DynamicOracleConnection(connStr:string) =
    let conn = new OracleConnection(connStr)
    member private x.Connection = conn
//    static member (?) (conn:DynamicOracleConnection, name) =
//        let command = new OracleCommand(name, conn.Connection)
//        command.CommandType <- CommandType.Text 
//        new DynamicOracleCommand(command)
    member x.GenerateCommand(query:string) = 
        let command = new OracleCommand(query, conn)
        command.BindByName <- true
        new DynamicOracleCommand(command)
    member x.Open() = conn.Open()

    interface IDisposable with
        member x.Dispose() = conn.Dispose()

Usage

type Order = {
    OrderId: int
    Comments: string option}

let connectionString = 
    "User Id = XXXXXX; " + 
    "Password = XXXXXX; " + 
    "Data Source = " + 
        "(Description = " +
        "(Address = " + 
        "(Protocol = TCP) " + 
        "(Host = 127.0.0.1) " + 
        "(Port = 1521)) " + 
        "(Connect_Data = (Service_Name = XXXXXX)))"

let getOrderByIdQuery =
    "select * " +
    "from ORDERS " +
    "where IDORDER = : orderId " +
    "order by IDORDER"

let getOrderById (orderId: int) = seq {
    use conn = new DynamicOracleConnection(connectionString)
    use cmd = conn.GenerateCommand(getOrderByIdQuery)
    
    cmd?orderId <- orderId

    conn.Open()
    use reader = cmd.ExecuteReader ()

    while (reader.Read()) do
        yield {
            OrderId = defaultArg reader?IDORDER 0
            Comments = reader?COMMENTS}}

References

F# applications with icons

Icons of an F# WPF application

There are at least two different usages for icons. First, there is the application icon displayd on the top left corner of the application window and also in the task bar. Second, Windows Explorer shows application icons in the file lists and also uses them for shortcuts.

Application icon

  1. Create the icon. For example, save an image with Paint as a 256 color bitmap and change the file extension to *.ico
  2. Save the icon in an “Resources” subfolder of the project
  3. Set the Build Action of the icon to Resource
  4. Include the icon with the XAML file
    <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
            Icon="/Resources/MyIcon.ico" 
            Title="MyApplication" Height="600" Width="1000" >

Windows Explorer icon

  1. Save the icon in 3.00 format using the free tool @icon sushi.
  2. Create a text file with a *.rc extension. Save the *.rc file in the “Resources” subfolder.
  3. The *.rc file should contain a single line. The ‘1’ is part of the text and not a line number.
    1 ICON "MyIcon.ico"
  4. Ensure that the Resource Compiler rc.exe is installed. Adding the C++ Tools to Visual Studio should also install rc.exe.
  5. Compile the *.rc file with rc.exe into an *.res file.
  6. C:\Program Files (x86)\Windows Kits\10\bin\x86>rc.exe /v C:\pathToMyFile\Resources\MyResources.rc
  7. In Visual Studio, select the Application tab of project property page
  8. Select the compiled *.res file in the Resources section. The complete absolute path to the file is shown. Leave the tick box “Use standard resource names” unchecked.
  9. With a text editor edit the project *.fsproj file. Change the absolut path to a relative path.
    Visual Studio should be closed.
    <Win32Resource>Resources\MyResources.res</Win32Resource>

References

F# and Excel

Add a reference to the solution:
Project menu > Add Reference. On the COM tab, locate Microsoft Excel 16.0 Object Library. That’s the reference which contains Microsoft.Office.Interop.Excel.dll.
Also, add a reference to the office library Microsoft Office 16.0 Object Library.

#if INTERACTIVE
#r "Microsoft.Office.Interop.Excel"
#endif

open System
open Microsoft.Office.Interop.Excel

let excel = new ApplicationClass(Visible = false)
let workbook = excel.Workbooks.Open (templateFile)
//let workbook = excel.Workbooks.Add (xlWBATemplate.xlWBATWorksheet)
let worksheet = (workbook.Worksheets.[1] :?> Worksheet)

excel.Visible <- true
excel.Application.ScreenUpdating <- false

worksheet.Range("A9","E9").Value2 <- [| "This"; "is"; "my"; "test"; ":-)" |]

workbook.SaveAs(outputFile)

workbook.Close()
excel.Workbooks.Close()
excel.Application.Quit()

releaseObject excel

F# split text into sections

type Section (lines:string list) =
    member x.Lines = lines

/// Splits a sequence after the last line of a section defined by the predicate.
let splitAtEach (predicate: 'T -> bool) (xs : seq<'T>) : seq<'T list> =
    let section = new ResizeArray<'T>()
    seq {
        for x in xs do
            section.Add x
            if predicate x then 
                yield Seq.toList section
                section.Clear()
        if section.Count > 0 then
            yield Seq.toList section}

/// Splits a sequence before the first line of a section defined by the predicate.
let splitBeforeEach (predicate: 'T -> bool) (xs : seq<'T>) : seq<'T list> =
    let section = new ResizeArray<'T>()
    seq {
        for x in xs do
            if predicate x then 
                yield Seq.toList section
                section.Clear()
            section.Add x
        if section.Count > 0 then
            yield Seq.toList section}

usage:

open System.IO

let lines = File.ReadAllLines(myTextFile)

let getSections lines =
    splitAtEach isSectionLine lines
    |> Seq.map (fun x -> new Section(x))
    |> Seq.toList

This is a slightly modified version from: https://codereview.stackexchange.com/questions/55554/parsing-sections-from-a-log-file-using-f

F# get object data type

This function prints the data type of an object to the standard output.

let getObjectType (x:obj) =
    match x with
    | :? sbyte       as y -> printfn  "sbyte : %A" y
    | :? byte        as y -> printfn  "byte : %A" y
    | :? int16       as y -> printfn  "int16 : %A" y
    | :? uint16      as y -> printfn  "uint16 : %A" y
    | :? int32       as y -> printfn  "int32 : %A (int)" y
//    | :? int         as y -> printfn  "int : %A" y            
//    //   int is actually the same as int32
    | :? uint32      as y -> printfn  "uint32 : %A" y
    | :? int64       as y -> printfn  "int64 : %A" y
    | :? uint64      as y -> printfn  "uint64 : %A" y
    | :? bigint      as y -> printfn  "bigint : %A" y
    | :? float32     as y -> printfn  "float32 : %A" y
    | :? float       as y -> printfn  "float : %A" y
    | :? decimal     as y -> printfn  "decimal : %A" y
//    | :? BigRational as y -> printfn  "BigRational : %A" y    
//    //   BigRational requires a reference to FSharp.PowerPack.dll
    | :? char        as y -> printfn  "char : %A" y
    | :? string      as y -> printfn  "string : %A" y
    | :? bool        as y -> printfn  "bool : %A" y
    | :? System.DateTime as y -> printfn "DateTime : %A" y
    | _ -> printfn "unknown data type"

List of F# data types: https://www.tutorialspoint.com/fsharp/fsharp_data_types.htm