【问题标题】:Constructors as datatypes in Haskell构造函数作为 Haskell 中的数据类型
【发布时间】:2018-04-09 21:47:22
【问题描述】:

目前我有这些数据类型:

data NumberColumn = NumberColumn String [Double]
data StringColumn = StringColumn String [String]
data UnknownColumn = UnknownColumn String [String]

所有这些数据类型(还有其他数据类型,这些只是域示例)模型 csv 文件列。它们可以表示纯数字、姓名、金钱、简单的文本等。

我想要实现的是这样的:

data Column = NumberColumn String [Double] | StringColumn String [String] | UnknownColumn String [String]

也就是说,我想将它们放在一个单一的数据类型中,以便可以映射、过滤和创建新项目,如下所示:

sumColumn :: NumberColumn -> NumberColumn -> NumberColumn
sumColumn...

问题是 NumberColumn 不是数据类型而是构造函数,所以我能想到的最好的就是接受和返回 Column 类型:

sumColumn :: Column -> Column -> Column
sumColumn (NumberColumn...) (NumberColumn...)...

这可行,但函数应该只使用 NumberColumns 的明确性丢失了,我非常想保留它。

这可以实现吗?

【问题讨论】:

    标签: haskell


    【解决方案1】:

    您似乎想要一个单一类型的构造函数 Column 定义为

    data Column a = Column String [a]
    

    然后

    sumColumn :: Column Double -> Column Double -> Column Double
    sumColumn (Column name values) = ...
    

    要区分 StringColumnUnknownColumn 与原始字符串,请为 Unknown 使用新类型以将其与“普通”字符串区分开来。

    newtype Unknown = Unknown String
    
    type UnknownColumn = Column Unknown  -- for example
    

    【讨论】:

    • 如果我遗漏了什么,我很抱歉,但这如何让我保留不同列的列表?
    • 从问题中看不出这是一项要求。
    • @PetrasPurlys 不幸的是,这两个特性是不兼容的:要么类型系统区分这两个东西,所以你可以编写一个只接受数字列的函数,或者类型系统不区分这两个东西,所以你可以把它们都放在一个列表中。如果您想要这两种功能,则需要两种(集合)类型,一种可区分,一种不可区分。
    • @PetrasPurlys 您可以将a 向右移动并取回您想要的力量。 data Column = forall a. ToColumn a => Column a(但你会在类型签名中丢失和跟踪a
    【解决方案2】:

    因此,您可以将NumberColumn 构造函数的数据分解为新的数据类型:

    data Column
        = NumberColumn  NumCol
        | StringColumn  String [String]
        | UnknownColumn String [String]
    
    data NumCol = NumCol String [Double]
    

    sumColumn 仅在NumCols 上定义,而不是Columns 或NumberColumns:

    sumColumn :: NumCol -> NumCol -> NumCol
    sumColumn (NumCol s1 d1) (NumCol s2 d2) = ...
    

    编辑:

    如果你想让NumCols 表现得像NumberColumns,你可以使用类型类:

    class Columnlike a where
        toColumn :: a -> Column
    
    instance Columnlike Column where
        toColumn = id
    
    instance Columnlike NumCol where
        toColumn = NumberColumn
    

    有了这个类型类,你在Column 上的函数现在可以在一些Columnlike a 上,你可以互换使用NumCols 和Columns。例如:

    colFunction :: Column -> Column
    colFunction = ...
    

    变成

    colFunction :: Columnlike a => a -> a
    colFunction = ...
    

    然后您可以在NumCols 和NumberColumns 上使用colFunction

    【讨论】:

    • 这可行,但这是一个可读性问题。 NumCol 和 NumberColumn 是/代表相同的东西,所以我非常希望有一个名称。
    • @PetrasPurlys 那里可能没有免费的午餐。但是,您可以使 NumColNumberColumn 行为相似,请参阅我的编辑。另一种选择是在对 (String, [Double]) 上定义 sumColumn,但这也有其缺点。
    【解决方案3】:

    除了chepner的

    data Column a = Column String [a]
    

    您可以将sumColumn 定义为

    sumColumn :: Num a => Column a -> Column a -> Column a
    sumColumn (Column name1 ms) (Column name2 ns) =
      Column (name1 ++ "+" ++ name2) (zipWith (+) ms ns)
    

    您还可以使用 GADT 来确保包含数字的列仅按原样使用:

    {-# LANGUAGE GADTs #-}
    
    data Column a where
      NumColumn :: Num a => String -> [a] -> Column a
      StrColumn :: String -> [String] -> Column String
    

    但您仍然必须限制在 Num a => Column as 上运行的每个函数。

    【讨论】:

      猜你喜欢
      • 2013-08-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多