【问题标题】:F# generic type with static members. Is that ever possible?具有静态成员的 F# 泛型类型。那有可能吗?
【发布时间】:2017-12-10 01:19:20
【问题描述】:

我有以下情况:

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# 引擎,它进行一些计算,以及一个网站。 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。 …除了网络团队不想在每次调用泛型工厂的方法时都使用实例。所以,我最终写了:

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 s),那么泛型工厂的所有实现都必须手动更新。

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

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

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

非常感谢。

【问题讨论】:

  • 这个问题太长太复杂了。试着想出一个更简单、更小的例子来说明你需要什么和你拥有什么。此外,代码格式已全部关闭。此外,“работает как часы”这一表达并不直接翻译成英语。尝试“像发条一样”或“像魅力一样”。

标签: generics f#


【解决方案1】:

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

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

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 成员进行编码,而不是预先计算这些查找。它们可能足够小,以至于性能差异不足以证明维持这种更复杂的设置是合理的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多