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

标签 generics f#

我有以下情况:

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

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

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# 引擎(执行一些计算)和网站。 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)

然后自动获取可能需要的所有内容,包括所有静态成员。我想知道这是否可能,如果是,那么具体如何实现。

非常感谢。

最佳答案

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

一种方法是拥有一个带有工厂的模块,也许是延迟评估的(尽管我不认为这在您的场景中是一个问题,但创建它们并不昂贵)。然后您可以直接使用该模块,或者通过您想要创建的类型的静态成员重新公开它,例如

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

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

但说实话,您可能可以直接针对所有成员进行编码,而不是预先计算这些查找。它们可能足够小,以至于性能差异不足以证明维护这种更复杂的设置是合理的。

关于generics - 具有静态成员的 F# 泛型类型。这可能吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47723924/

相关文章:

swift - 无法将类型 'GenericClass<Class>' 的值转换为预期的参数类型 'GenericClass<Class>?'

c# - 如何将通用类型参数的值转换为具体类型?

.net - 在 F# 中,是否有使用用于相等和排序比较的主键创建记录类型的快捷方式?

f# - 使用计算表达式组合引用函数

Azure函数预编译函数,HttpRequestMessage未传递给函数

c# - 类型为 IEnumerable<IEnumerable<string>> 的参数接受 List<List<string>> 而 IEnumerable<(int, IEnumerable<string>)> 不接受?

具有通用键的 C++ std::map 类

java - 使用带有扩展的泛型的接口(interface)中重写方法的返回类型的意外行为

for-loop - 为什么简单的 for 循环表达式仅限于整数范围?

f# - 在 F# : is Set adequate? 中采样