Dictionary 处理相同类型但形状不同的对象的惯用方法是什么?

Dictionary 处理相同类型但形状不同的对象的惯用方法是什么?,dictionary,f#,discriminated-union,Dictionary,F#,Discriminated Union,我有一个地图,它的键可以有多种形状(我用的是“形状”这个词比较随便)。为了处理这个问题,我创建了一个歧视联盟(DU): 可以从动态创建的密钥中检索信息: let vols = auxData.[AuxDataKey.TwoStringsInt("Vol", ticker, count)] 这是可行的,但我觉得很麻烦 一种选择是创建另一个DU: type StringInt = String of string | Int of int 然后让贴图键成为这些对象的列表。无需为映射键装箱DU,S

我有一个地图,它的键可以有多种形状(我用的是“形状”这个词比较随便)。为了处理这个问题,我创建了一个歧视联盟(DU):

可以从动态创建的密钥中检索信息:

let vols = auxData.[AuxDataKey.TwoStringsInt("Vol", ticker, count)]
这是可行的,但我觉得很麻烦

一种选择是创建另一个DU:

type StringInt = String of string | Int of int
然后让贴图键成为这些对象的列表。无需为映射键装箱DU,
StringInt
DU更简单。另一方面,这种选择看起来不太安全,因为编译器可以接受列表,例如,其中第一个元素是Int,第二个是字符串,这不适合
AuxDataKey
DU中的任何情况。我没有在实践中尝试过这一点,因为可能有更好的选择


有没有一种惯用的方法来处理
必须是同一类型但形状不同的对象?

您这样做的方式非常惯用。请注意,在引用DU案例时,不需要在类型名称
AuxDataKey
前面加前缀,因此您只需编写:

let key = TwoStringsInt("Vol", ticker, count)
可以将类型分解为有意义的部分,但是如果不知道组件部分的真正含义,就很难判断。这可以消除一些重复。例如,只要拔出所有案例都有共同点的一个字符串,就会得到以下结果:

type SubKey =
    | A of string
    | B of int
    | C of string * int
    | D of int * int
    | E of string * int * int

type AuxDataKey =
    { Key : string
      SubKey : SubKey }

其他一些语言的特性是,类型系统根据返回的不同值自动推断联合用例。我相信TypeScript可以做到这一点,OCaml的多态变体也有类似之处。然而,在这方面,F#在名义上是完全类型化的,这意味着每个DU案例在使用之前都必须定义和命名。

我知道您真正想要的是这样的东西:

type AuxDataKey = 
    | Vol of string * int
    | AnotherKey of string * int * int * string
    | ...
let x = { arg1 = "test"; arg2 = 42 } :> IAuxDataKey

match x with
| VolKey (arg1, arg2) -> printfn "arg1: %s, arg2: %d" arg1 arg2
| _ -> printfn "unknown"
但是有这么多的案例让人觉得这是一个毫无意义的练习

从你的问题对我的理解来看,你感觉到了站在错误的一边的痛苦。你有一个不同类型的开放宇宙,你试图在它们的形状中找到一种模式,这将使使用一个(封闭的)有区别的联合来表示它们成为可能

F#通过使用该语言的OO特性,为您提供了一种简单的切换双方的方法。我以前使用和看到的模式是创建一个开放的类型层次结构,继承一个公共基类型(接口或抽象类),并使用部分活动模式对其进行扩充,这些模式为您提供了一个API来访问它们,在F#中感觉非常自然

然后像这样使用它们:

type AuxDataKey = 
    | Vol of string * int
    | AnotherKey of string * int * int * string
    | ...
let x = { arg1 = "test"; arg2 = 42 } :> IAuxDataKey

match x with
| VolKey (arg1, arg2) -> printfn "arg1: %s, arg2: %d" arg1 arg2
| _ -> printfn "unknown"
IAuxDataKey
在这里只是一个标记接口,但是如果在您的上下文中有意义的话,您可以在那里添加公共成员(例如,如果键来自同质源,则可以使用parse或create方法)


在这里,您失去的是进行详尽匹配的能力,您将需要“一网打尽”的情况。但我认为在您的用例中,这并不是一个很大的问题。

对于当前的解决方案,您到底不喜欢什么?在一个理想的世界里,你会觉得它怎么样?我没有什么大的抱怨。一个小问题是,如果我提出一个新的案例(例如,ThreeStringsTwoInts),我将不得不更改DU代码。但这是DU的工作方式,也有好的一面(安全性)。我在问题中提到了一个替代方案,我想可能还有一个更好的替代方案我还没有找到。在理想情况下,我只需编写
AuxDataKey(“Vol”,3)
,而不必指定
OneStringOneInt
。想一想,也许可以用一个具有多个构造函数的类来实现它。它根本不是惯用的,但我打赌你可以用重载的
成员来实现IDictionary。然后你可以说
auxData.[(“Vol”,ticker,count)]
。老实说,对于每种类型的键,我可能会有一个单独的
Map
。显然,在编译时您知道使用哪一个。然后,如果需要抓取所有值(最终将是连接在一起的所有
Map
s中的值)或典型的
Map.Map
(需要将映射函数一起应用于所有
Map
s),只需添加一些帮助函数即可我的问题不是我有太多不同的案例。您的
let x=…
行中的代码并不比我的
let key=…
中的代码简单,特别是在遵循Quick Brown Fox的建议删除类型名
AuxDataKey
之后。我不需要分解对象,实际上我必须在另一个范围内重新构造它。因此,我将把你的想法储存起来,这很有趣,以便在可能有帮助的情况下再讨论。
let x = { arg1 = "test"; arg2 = 42 } :> IAuxDataKey

match x with
| VolKey (arg1, arg2) -> printfn "arg1: %s, arg2: %d" arg1 arg2
| _ -> printfn "unknown"