具有静态成员的F#泛型类型。这有可能吗?

时间:2017-12-09 00:11:40

标签: generics f#

我有以下情况:

1。 有许多(20 - 40)非常类似的结构缓慢变化的数据类型。

2。 低级别的每种数据类型由唯一(对于类型)字符串标签和唯一(对于类型)键(通常为字符串或长)表示。密钥不会改变,但标签(平均)可能每年更新不到一次。

3。 我将每个这样的数据类型“提升”为F#DU,因此对于每个标签/键,都存在DU情况。这是必需的,因为一些更高级别的代码需要进行模式匹配。所以,在极少数情况下,当类型更新时,我想要一个编译错误,万一事情发生变化。因此,对于每个这样的类型,我生成/编写代码如:

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。 所有这些类型都在两个地方消耗:F#engine,它进行一些计算,以及一个网站。 F#引擎只适用于DU情况。不幸的是,这些天的网站主要基于字符串。因此,Web团队需要基于字符串的方法来处理几乎所有类型的操作,并且他们正在使用C#。

5。 所以,我创建了两个通用工厂:

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。 在这一点上,如果我为每种类型创建一个具有单例的通用工厂,那么一切都可以作为时钟工作,如:

type CollegeSizeFactory private () =
    inherit UnionFactory<CollegeSize, string, int64> (CollegeSize.all)
    static let instance = CollegeSizeFactory ()

7。 ...除了Web团队每次调用泛型工厂的方法时都不想使用Instance。所以,我最后写了:

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

问题在于,如果明天他们需要更多的快捷方式(例如静态成员FromLabel),那么必须手动更新通用工厂的所有实现。

理想情况下,我想要一个单行,这样对于我需要的每个工厂,我都可以继承一般类型,例如:

type CollegeSizeFactory private () =
    inherit UnionFactory<CollegeSize, string, int64> (CollegeSize.all)

然后自动获取可能需要的所有内容,包括所有静态成员。我想知道这是否有可能,如果是的话,那究竟是怎么回事。

非常感谢。

1 个答案:

答案 0 :(得分:3)

我建议你放弃单身人士的想法,而是使用实例成员,完全避免这个问题。单身人士从一开始就是一个坏主意。

一种方法是建立一个带有工厂的模块,或许可以懒惰地评估(虽然我不认为这是你方案中的一个问题,但创建它们并不昂贵)。然后你可以直接使用那个模块,或者通过你想要创建的类型的静态成员重新暴露它,例如。

type CollegeSizeFactory () =
    inherit UnionFactory<CollegeSize, string, int64> (CollegeSize.all)

module Factories = 
    let collegeSize = lazy CollegeSizeFactory()

type CollegeSize with
    static member Factory = Factories.collegeSize.Value

CollegeSize.Factory.tryFromKey 1

你的问题的设计空间相当大,毫无疑问,这不是最好的解决方案,但我觉得这是一个很好的第一步,可以通过简单的重构来实现。

但说实话,你可能会直接对你的all成员进行编码,而不是预先计算这些查找。它们可能足够小,以至于性能差异不足以维持这种更复杂的设置。