作为 Data.Map 值的异构类型

gap*_*ppy 5 haskell map

我希望能够做这样的事情,这显然是不允许的。

import qualified Data.Map as Map
x = Map.fromList [ 
      ("Name",    ["John", "Steve", "Kelly", "Zoe"]), 
      ("Surname", ["Smith", "Lee", "Ng", "White"]), 
      ("Age",     [1, 2, 3, 4]) 
    ]
Run Code Online (Sandbox Code Playgroud)

一种可能的解决方案是创建这样的类型:

data L = LInt [Int] | LString [String]

x = Map.fromList [ 
      ("Name",    LString ["John", "Steve", "Kelly", "Zoe"]),
      ("Surname", LString ["Smith", "Lee", "Ng", "White"]), 
      ("Age",     LInt    [1, 2, 3, 4]) 
    ]
Run Code Online (Sandbox Code Playgroud)

这是不可取的。有没有更好的方法?

我的实际用例:非常广泛,我正在尝试实现 dplyr ( http://cran.r-project.org/web/packages/dplyr/vignettes/introduction.html ) 的一部分,可以将其视为“语法数据的”。Dplyr 具有用于表格数据的列和行选择以及行的分组和连接的表达式,其中行数可以是数百万,列数可以是数百。我现在关心的不是数据大小,而是生成与该包类似的功能和工作流程的能力

n. *_* m. 3

您可以为此使用存在量化

{-# LANGUAGE ExistentialQuantification, FlexibleInstances, TypeSynonymInstances #-}

import qualified Data.Map as Map

class ColumnData c
instance ColumnData Integer
instance ColumnData String

data Column = forall c . ColumnData c => Column [c]

x = Map.fromList [
        ("Name",    Column ["John", "Steve", "Kelly", "Zoe"]),
        ("Surname", Column ["Smith", "Lee", "Ng", "White"]),
        ("Age",     Column [1::Integer, 2, 3, 4])
    ]
Run Code Online (Sandbox Code Playgroud)

(请注意,在最后一列中,我们需要显式类型注释,因为文字已重载,而存在主义无法完全应对这一点)。

当然,现在无法从 中获得映射数据的真实类型x。你只有forall a . a => Column a,而且你唯一能用它做的就是调用 的方法class Column。本示例未定义任何内容。您应该添加自己的方法,并且必须明智地选择。

请注意,由于我不太明白的原因,许多铁杆 Haskellers 都不赞成这种技术。如果有人能阐明这一点,那就太好了。

  • 这确实是一个热门话题。在我看来,存在类型是否是一个好的答案很大程度上取决于“ColumnData a”类的方法是什么。例如,如果您拥有的只是 `foo :: a->Int`,那么使用 `[Int]` 比使用 `Column` 更简单,并且依赖于惰性。在这种情况下,我相信OP想要取回字符串和整数,因此我们需要部分或“也许”返回的getter,每种类型都有一个getter。如果我们有很多类型,我们甚至可以转向“Typeable”,并且本质上使用动态类型。根据实际情况,我可能仍然更喜欢 sum 类型。 (3认同)
  • 对我来说,相对较小的、编译时已知的类型列表听起来很像求和类型,并且它可以为您省去很多麻烦。我看不出 Typabale 相对于总和类型有何性能优势,特别是如果您只为每列标记一次(即强制列类型中的列同质性)。因此,除非您确实需要任意类型,而不是仅仅觉得一切都应该是对象,否则总和类型及其标签更容易。 (2认同)