如何从联合类型中解开泛型类型别名,从而使类型别名更具体?

Posted

技术标签:

【中文标题】如何从联合类型中解开泛型类型别名,从而使类型别名更具体?【英文标题】:How do I unwrap a generic type alias from a union type which makes the type alias more specific? 【发布时间】:2019-07-30 17:27:18 【问题描述】:

我有类型Model,它描述了泛型别名ModelFields 的两种可能状态。我想从Model 类型的实例中提取通用ModelFields 记录。

type Model endValue stats
  = ShowEndValues (ModelFields Organism endValue)
  | ShowStatistics (ModelFields Rank stats)

type alias ModelFields object results =
   results : List results
  , objects : List object
  , cellValue : results -> String
  , location : Maybe (Rank, Rank)
  

getModelFields : Model endValue stats -> ModelFields object results
getModelFields model =
  case model of
    ShowEndValues modelFields ->
      modelFields 
    ShowStatistics modelFields ->
      modelFields

但 Elm 不允许它对每个 case 表达式都说

TYPE MISMATCH - Something is off with the 1st branch of this `case` expression:

55|       modelFields 
          #^^^^^^^^^^^#
This `modelFields` value is a:

    ModelFields #Organism# #endValue#

But the type annotation on `getModelFields` says it should be:

    ModelFields #object# #results#

#Hint#: Your type annotation uses type variable `object` which means ANY type of
value can flow through, but your code is saying it specifically wants a
`Organism` value. Maybe change your type annotation to be more specific? Maybe
change the code to be more general?

所以我的问题是:我如何从Model 获得ModelFields?还是我在做一些根本上存在缺陷的事情?

UPD。我正在尝试建模的详细信息。

我有Organism 类型的对象。它们被分组为Ranks。我的服务器对Organisms 进行了一些成对分析,例如在两个Organisms 之间计算SimilarityDistance。我想在不同页面的两个表中显示这些分析的结果,一页用于Similarity 分析,另一页用于Distance 分析。这意味着该表应该可重复使用以接受任何形式的分析结果。 另一方面,这些表有一个共同的模式。它们可以处于两种状态:

    显示Organisms 之间成对比较的具体结果(Model 中的endValue),因此表格的行和列代表Organisms。 在Organisms 的组 (Ranks) 之间显示统计值(例如,平均值或标准差,stats in Model),在这种情况下,表格的行和列代表Ranks。

显示哪个Ranks 或Organisms 取决于ModelFieldslocation 字段。用户可以单击一个按钮,我想在我的update 函数中更改location。此导航更改可能会在ShowEndValuesShowStatistics 状态之间切换。这就是为什么我试图从Model 构造函数中解开ModelFields。我附上一个简单的插图,希望它有助于澄清。

【问题讨论】:

这是an XY problem。你试图做的事情是不可能的,不清楚你想在更高的层次上完成什么,为什么你做出了你做出的选择来达到这个目标,主要是因为不清楚你为什么需要所有这些类型变量。如果您能解释这一点,则可能会建议您改用什么方法,但除此之外,很难给出“不,行不通”以外的答案,并像 Jan 那样详细说明其技术原因。 【参考方案1】:

这确实是不可能的。

getModelFields 函数中的model 值将具有Model endValue stats 类型。这意味着它要么是ShowEndValues 变体,包含ModelFields Organism endValue 类型的值,要么是ShowStatistics 变体,包含ModelFields Rank stats 类型的值。 case 表达式的每个分支都解包一个变体并输出 modelFields 值。

现在,让我们尝试确定case 表达式的类型。我们查看每个分支返回的类型,并尝试找到包含这两种类型的类型(统一它们)。乍一看,它看起来很有希望:两个值都采用ModelFields a b 的形式,因此我们将递归地尝试统一每个子类型。这里我们遇到了一个问题——第一种类型的a 的类型为Organism,而第二种类型为Rank。没有OrganismRank 的类型,所以编译失败。

注意:从错误信息中可以看出,Elm 实际上试图将分支的类型与函数结果类型统一起来。 (我描述了另一个方向,因为我认为它更容易理解。)Elm 遵循的方向也失败了,因为它会递归并尝试将具体的 Organism 类型与通用的 object 类型统一起来,类似于我们尝试的方式统一两种具体类型。

【讨论】:

谢谢你的解释,Jan。所以我知道这是不可能的。我更新了我的问题。您能就如何更好地构建模型提供任何建议吗? 如果来自两个分支的数据都可以用于渲染到一个表中,该函数应该将其转换为该表可以使用的通用类型,例如Model endValue stats -> StatsMatrix

以上是关于如何从联合类型中解开泛型类型别名,从而使类型别名更具体?的主要内容,如果未能解决你的问题,请参考以下文章

数据类型的别名

TypeScript 如何为泛型函数创建泛型类型别名?

Java 泛型的类型别名

C++ Primer 5th笔记(chap 16 模板和泛型编程)模板类型别名

类型别名的无效重新声明

Typescript 别名泛型接口