I am trying to refactor some existing code into a more monodic approach. Existing code contains interfaces IXInterface
and numerics like int
and bool
. The numerics already have Zero
by default, the interfaces have it as a property gettor, but bool
and string
do not. One way out is to wrap bool and string in an interface, but this is cumbersome.
I figured if the F# language manages to extend the types for numerics, perhaps I can do it too for strings and bools for my particular situation.
module MyZero =
let inline get_zero () : ^a = ((^a) : (static member get_Zero : unit -> ^a)())
type System.String with
static member get_Zero() = System.String.Empty
type XR<'T when 'T : (static member get_Zero : unit -> 'T)> =
| Expression of (SomeObj -> 'T)
| Action of (int -> 'T)
| Value of 'T
| Empty
member inline this.Execute(x: SomeObj) : 'T =
match this with
| Value(v) -> v
| Expression(ex) -> ex x
| Action(a) -> a x.GetLocation
| Empty -> get_zero()
static member map f x=
match x with
| XR.Empty -> XR.Empty
| XR.Value v -> XR.Value <| f v
| XR.Action p -> XR.Action <| fun v -> f (p v)
| XR.Expression e -> XR.Expression <| fun x -> f (e x)
// etc
The above compiles fine, as long as I don't try to use it with strings or bools:
type WihtBool = XR<int> // succeeds
type WihtBool = XR<IXInterface> // succeeds
type WihtBool = XR<bool> // fails
type WithString = XR<string> // fails
The error is clear and correct (I have an extension method, which is not recognized for obvious reasons), I just don't know a non-intrusive way to get rid of it:
fails with "the type bool does not support the operator 'get_Zero'
fails with "the type string does not support the operator 'get_Zero'
F# manages to extend numeric types using static optimizations which is a feature that is disabled outside the F# Core Library.
AFAIK the only way to get a similar mechanism is by using overloads and static member constraints.
Indeed what you are trying to do it's already implemented in F#+
#nowarn "3186"
#r @"FsControl.Core.dll"
#r @"FSharpPlus.dll"
open FSharpPlus
let x:string = mempty()
// val x : string = ""
type Boo = Boo with
static member Mempty() = Boo
let y:Boo = mempty()
// val y : Boo = Boo
It works on the same principle as the F# math operators where the static constraint can be satisfied by the type of any argument.
Here's the part of the source code that makes this magic.
Currently an instance for bool
is missing, but you can add an issue suggesting it or a pull request, it will be a one-liner (or two).
Anyway if you want to capture this functionality try this quick-standalone code:
type Mempty =
static member ($) (_:Mempty, _:string) = ""
static member ($) (_:Mempty, _:bool) = false
let inline mempty() :'t = Unchecked.defaultof<Mempty> $ Unchecked.defaultof<'t>
let x:string = mempty()
// val x : string = ""
let y:bool = mempty()
// val y : bool = false
type Boo = Boo with
static member ($) (_:Mempty, _:Boo) = Boo
let z:Boo = mempty()
// val z : Boo = Boo
You can rename Mempty
to get_Zero
but I think get_Zero
is not the best name for a monoid, remember that the number one under multiplication is also a monoid and get_Zero
is already used in F# Core libraries for generic numbers.
But honestly if you are going in this direction I strongly advise you to consider that library since there are many issues you may find when scaling your code already resolved there, you get for free other monoid related functions, like mconcat
and mfold
and you get nicer signatures on your types.