summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Stålnacke <joel@saker.fi>2026-02-13 20:48:51 +0200
committerJoel Stålnacke <joel@saker.fi>2026-02-13 20:48:51 +0200
commit58aa302f49311475cce11dcbd477df217e790ea6 (patch)
treed61bf5a2ebcf7004ab411b555a9183b9781c0888
Initial commitHEADmaster
-rw-r--r--.gitignore2
-rw-r--r--FunctionalObjects.slnx10
-rw-r--r--FunctionalObjects/FunctionalObjects.fsproj12
-rw-r--r--FunctionalObjects/Library.fs3
-rw-r--r--examples/Basic/FunctionalObjects.Examples.Basic.fsproj16
-rw-r--r--examples/Basic/Program.fs40
-rw-r--r--examples/DependencyInjection/FunctionalObjects.Examples.DependencyInjection.fsproj16
-rw-r--r--examples/DependencyInjection/Program.fs51
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"