I am designing a data-structure for interacting with a C# API from F#. Broadly speaking, it is a strongly-typed collection of components (ComponentCollection
), where components may have different types.
A first-pass looks like this:
type Foo =
{
Foo : int
}
type Bar =
{
Bar : string
}
type ComponentCollection =
{
FooComponents : Map<Foo, Component<Foo>>
BarComponents : Map<Bar, Component<Bar>>
}
module ComponentCollection =
let addFoo foo comp xs =
{ xs with FooComponents = xs.FooComponents |> Map.add foo comp }
let addBar bar comp xs =
{ xs with BarComponents = xs.BarComponents |> Map.add bar comp }
let tryFindFoo foo xs =
xs.FooComponents |> Map.tryFind foo
let tryFindBar bar xs =
xs.BarComponents |> Map.tryFind bar
There are two problems with this design:
- Repetition of boiler-plate code (e.g.
addFoo
,addBar
,tryFindFoo
, ...) - The type of components is not extensible without changing the type
ComponentCollection
, e.g. a user cannot addQuxComponents : Map<Qux, Component<Qux>>
themselves
I can redesign things using interfaces, but this loses much of the type safety F# is famous for!
open System
type ComponentKey =
interface
inherit IComparable
end
type Foo =
{
Foo : int
}
with interface ComponentKey
type Bar =
{
Bar : string
}
with interface ComponentKey
type ComponentCollection =
{
Components : Map<ComponentKey, obj>
}
module ComponentCollection =
let addFoo (foo : Foo) (comp : Component<Foo>) xs =
{ xs with Components = xs.Components |> Map.add (foo :> ComponentKey) (comp :> obj) }
let addBar (bar : Bar) (comp : Component<Bar>) xs =
{ xs with Components = xs.Components |> Map.add (bar :> ComponentKey) (comp :> obj) }
let tryFindFoo (foo : Foo) xs =
xs.Components
|> Map.tryFind (foo :> ComponentKey)
|> Option.map (fun x -> x :?> Component<Foo>) // Big assumption!
let tryFindBar (bar : Bar) xs =
xs.Components
|> Map.tryFind (bar :> ComponentKey)
|> Option.map (fun x -> x :?> Component<Bar>) // Big assumption!
// User can easily add more in their own code
How can I design ComponentCollection
achieving type-safety and extensibility?