diff options
| author | Joel Stålnacke <joel@saker.fi> | 2026-02-13 20:48:51 +0200 |
|---|---|---|
| committer | Joel Stålnacke <joel@saker.fi> | 2026-02-13 20:48:51 +0200 |
| commit | 58aa302f49311475cce11dcbd477df217e790ea6 (patch) | |
| tree | d61bf5a2ebcf7004ab411b555a9183b9781c0888 | |
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | FunctionalObjects.slnx | 10 | ||||
| -rw-r--r-- | FunctionalObjects/FunctionalObjects.fsproj | 12 | ||||
| -rw-r--r-- | FunctionalObjects/Library.fs | 3 | ||||
| -rw-r--r-- | examples/Basic/FunctionalObjects.Examples.Basic.fsproj | 16 | ||||
| -rw-r--r-- | examples/Basic/Program.fs | 40 | ||||
| -rw-r--r-- | examples/DependencyInjection/FunctionalObjects.Examples.DependencyInjection.fsproj | 16 | ||||
| -rw-r--r-- | examples/DependencyInjection/Program.fs | 51 |
8 files changed, 150 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd42ee3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/ +obj/ diff --git a/FunctionalObjects.slnx b/FunctionalObjects.slnx new file mode 100644 index 0000000..52dd1b1 --- /dev/null +++ b/FunctionalObjects.slnx @@ -0,0 +1,10 @@ +<Solution> + <Folder Name="/examples/" /> + <Folder Name="/examples/Basic/"> + <Project Path="examples/Basic/FunctionalObjects.Examples.Basic.fsproj" /> + </Folder> + <Folder Name="/examples/DependencyInjection/"> + <Project Path="examples/DependencyInjection/FunctionalObjects.Examples.DependencyInjection.fsproj" /> + </Folder> + <Project Path="FunctionalObjects/FunctionalObjects.fsproj" /> +</Solution> diff --git a/FunctionalObjects/FunctionalObjects.fsproj b/FunctionalObjects/FunctionalObjects.fsproj new file mode 100644 index 0000000..e08f188 --- /dev/null +++ b/FunctionalObjects/FunctionalObjects.fsproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net10.0</TargetFramework> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + </PropertyGroup> + + <ItemGroup> + <Compile Include="Library.fs" /> + </ItemGroup> + +</Project> diff --git a/FunctionalObjects/Library.fs b/FunctionalObjects/Library.fs new file mode 100644 index 0000000..141f78c --- /dev/null +++ b/FunctionalObjects/Library.fs @@ -0,0 +1,3 @@ +module FunctionalObjects.Object + +let derive f o = f o diff --git a/examples/Basic/FunctionalObjects.Examples.Basic.fsproj b/examples/Basic/FunctionalObjects.Examples.Basic.fsproj new file mode 100644 index 0000000..806da26 --- /dev/null +++ b/examples/Basic/FunctionalObjects.Examples.Basic.fsproj @@ -0,0 +1,16 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net10.0</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <Compile Include="Program.fs" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\FunctionalObjects\FunctionalObjects.fsproj" /> + </ItemGroup> + +</Project> diff --git a/examples/Basic/Program.fs b/examples/Basic/Program.fs new file mode 100644 index 0000000..a84f96e --- /dev/null +++ b/examples/Basic/Program.fs @@ -0,0 +1,40 @@ +open FunctionalObjects + +// A record is like an interface +type Printer = { + PrintMessage : string -> unit +} + +// Let's define a printHelloWorld function which uses the Printer interface. +// +// Notice that the type annotation is not required because Printer is a record +// and F# can infer the type of the variable based on the PrintMessage field. +// +// Because this pattern uses a concrete type for the interface, no runtime +// reflection is needed and type checking happens completely at compile time. +let printHelloWorld (printer : Printer) = + printer.PrintMessage "Hello World" + +// An instance of the record is a class aka an implementation of the interface +let basePrinter : Printer = + { PrintMessage = fun s -> printfn "Message: %s" s } + +printfn "Using the base printer:" +printHelloWorld basePrinter + +// We can create a sub-class of the base printer using Object.derive +// +// Object.derive is simply +// let derive f o = f o +// so it's completely syntactic sugar +let screamingPrinter : Printer = + basePrinter + |> Object.derive (fun b -> + { b with + // We choose which methods to override + PrintMessage = + fun s -> b.PrintMessage (s.ToUpper()) }) + +// printHelloWorld doesn't have to change when we switch the Printer. +printfn "Using the screaming printer:" +printHelloWorld screamingPrinter diff --git a/examples/DependencyInjection/FunctionalObjects.Examples.DependencyInjection.fsproj b/examples/DependencyInjection/FunctionalObjects.Examples.DependencyInjection.fsproj new file mode 100644 index 0000000..806da26 --- /dev/null +++ b/examples/DependencyInjection/FunctionalObjects.Examples.DependencyInjection.fsproj @@ -0,0 +1,16 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net10.0</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <Compile Include="Program.fs" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\FunctionalObjects\FunctionalObjects.fsproj" /> + </ItemGroup> + +</Project> diff --git a/examples/DependencyInjection/Program.fs b/examples/DependencyInjection/Program.fs new file mode 100644 index 0000000..84b51c6 --- /dev/null +++ b/examples/DependencyInjection/Program.fs @@ -0,0 +1,51 @@ +open System + +type Logger = { + Log : string -> unit +} + +type HelloService = { + SayHelloTo : string -> unit +} + +// This is the constructor of a logger that adds a timestamp in the specified +// format to each log message it writes. +let dateTimeLogger (dateFormat : string) = + { Log = + fun msg -> + let timestamp = DateTime.Now.ToString dateFormat + printfn "[%s]: %s" timestamp msg } + +let logger = dateTimeLogger "yyyy-MM-dd hh:mm:ss" + +// This implementation of Logger ignores all messages +let noopLogger = + { Log = ignore } + +let doSomething logger = + logger.Log "Starting to do something" + let x = 1 + 1 + sprintf "Did something! Result: %d" x + |> logger.Log + +// Implementations can be injected to application code +doSomething logger +doSomething noopLogger + +// Implement HelloService. Our implementation depends on a Logger to write +// messages to. +let helloService logger = + { SayHelloTo = + fun user -> + sprintf "Saying hi to %s" user + |> logger.Log + printfn "Hi %s!" user + logger.Log "Operation complete" } + +// Create an instance of helloService. +// +// We don't want to do anything with the messages so we discard them by using +// injecting noopLogger as the implementation of Logger. +let instance = helloService noopLogger + +instance.SayHelloTo "John" |
