I have the following situation:
1. There are many (20 - 40) very similarly structured slowly changing data types.
2. Each data type at the low level is represented by a unique (for a type) string label and a unique (again, for a type) key (usually string or long). The key does not change, but the label (on average) may be updated less than once a year.
3. I "promoted" each such data type to F# DU, so that for each label/key there is a DU case. This is needed because some of the higher-level code needs to do a pattern matching. So, in a rare case when the types get updated, I want a compile error in case the things change. So, for each such type I generate/write the code like:
type CollegeSize =
| VerySmall
| Small
| Medium
| Large
| VeryLarge
with
static member all =
[|
(CollegeSize.VerySmall, "Very small (less than 2,000 students)", 1L)
(CollegeSize.Small, "Small (from 2,001 to 5,000 students)", 2L)
(CollegeSize.Medium, "Medium (from 5,001 to 10,000 students)", 3L)
(CollegeSize.Large, "Large (from 10,000 to 20,000 students)", 4L)
(CollegeSize.VeryLarge, "Very large (over 20,000 students)", 5L)
|]
4. All such types are consumed in two places: F# engine, which does some calculations, and a web site. The F# engine just works with DU cases. The web sites these days, unfortunately, are mostly based on strings. So, the web team wanted a string based methods for almost any actions with the types and they are using C#.
5. So, I created two generic factories:
type CaseFactory<'C, 'L, 'K when 'C : comparison and 'L : comparison and 'K : comparison> =
{
caseValue : 'C
label : 'L
key : 'K
}
// DU factory: 'C - case, 'L - label, 'K - key
type UnionFactory< 'C, 'L, 'K when 'C : comparison and 'L : comparison and 'K : comparison> (all : array< 'C * 'L * 'K> ) =
let map = all |> Array.map (fun (c, l, _) -> (c, l)) |> Map.ofArray
let mapRev = all |> Array.map (fun (c, l, _) -> (l, c)) |> Map.ofArray
let mapVal = all |> Array.map (fun (c, _, k) -> (c, k)) |> Map.ofArray
let mapValRev = all |> Array.map (fun (c, _, k) -> (k, c)) |> Map.ofArray
let allListValue : System.Collections.Generic.List<CaseFactory< 'C, 'L, 'K>> =
let x =
all
|> List.ofArray
|> List.sortBy (fun (_, s, _) -> s)
|> List.map (fun (c, s, v) -> { caseValue = c; label = s; key = v } : CaseFactory< 'C, 'L, 'K>)
new System.Collections.Generic.List<CaseFactory< 'C, 'L, 'K>> (x)
//Use key, NOT label to create.
[<CompiledName("FromKey")>]
member this.fromKey (k : 'K) : 'C = mapValRev.Item (k)
// For integer keys you can pass "1" instead of 1
[<CompiledName("FromKeyString")>]
member this.fromKeyString (s : string) : 'C = this.fromKey (convert s)
//Use key, NOT label to create.
[<CompiledName("TryFromKey")>]
member this.tryFromKey (k : 'K) : 'C option = mapValRev.TryFind k
[<CompiledName("TryFromKeyString")>]
member this.tryFromKeyString (s : string) : 'C option = mapValRev.TryFind (convert s)
//Use key, NOT label to create.
[<CompiledName("TryFromKey")>]
member this.tryFromKey (k : 'K option) : 'C option =
match k with
| Some x -> this.tryFromKey x
| None -> None
[<CompiledName("AllList")>]
member this.allList : System.Collections.Generic.List< CaseFactory< 'C, 'L, 'K>> = allListValue
[<CompiledName("FromLabel")>]
member this.fromLabel (l : 'L) : 'C = mapRev.[l]
[<CompiledName("TryFromLabel")>]
member this.tryFromLabel (l : 'L) : 'C option = mapRev.TryFind l
[<CompiledName("TryFromLabel")>]
member this.tryFromLabel (l : 'L option) : 'C option =
match l with
| Some x -> this.tryFromLabel x
| None -> None
[<CompiledName("GetLabel")>]
member this.getLabel (c : 'C) : 'L = map.[c]
[<CompiledName("GetKey")>]
member this.getKey (c : 'C) : 'K = mapVal.[c]
6. At this point everything works as a clock if I create a generic factory with the singleton for each type like:
type CollegeSizeFactory private () =
inherit UnionFactory<CollegeSize, string, int64> (CollegeSize.all)
static let instance = CollegeSizeFactory ()
7. … except that web team does not want to use Instance each and every time when they call methods of a generic factory. So, I ended up writing:
type CollegeSizeFactory private () =
inherit UnionFactory<CollegeSize, string, int64> (CollegeSize.all)
static let instance = CollegeSizeFactory ()
static member Instance = instance
static member FromLabel s = CollegeSizeFactory.Instance.fromLabel s
static member FromKey k = CollegeSizeFactory.Instance.fromKey k
static member FromKeyString s = CollegeSizeFactory.Instance.fromKeyString s
The problem with that is that if tomorrow they need a few more shortcuts (like static member FromLabel s) then all implementations of generic factory must be updated by hands.
Ideally, I want a one-liner so that for each factory that I need I could just inherit from a generic type, like:
type CollegeSizeFactory private () =
inherit UnionFactory<CollegeSize, string, int64> (CollegeSize.all)
and then automatically get everything that could be possibly needed, including all static members. I wonder if this is ever possible, and if yes, then how exactly.
Thanks a lot.