12

I'm trying to model some polymorphic-type data in haskell. I understand why the following code doesn't work, but I'm hoping it illustrates what I'm trying to do. My question is: what is an idiomatic way to model this with Haskell? (You don't need to keep the input format the same if there is a better way - I don't have any existing code or data.)

data Running = Sprint | Jog deriving (Show)
data Lifting = Barbell | Dumbbell deriving (Show)

data Time   = Time   Integer deriving (Show)
data Pounds = Pounds Integer deriving (Show)

data TimedActivity    = TimedActivity Running Time deriving (Show)
data WeightedActivity = WeightedActivity Lifting Pounds deriving (Show)

class Activity a

instance Activity TimedActivity
instance Activity WeightedActivity

-- I have a list of activities
main :: IO ()
main = putStrLn $ show [ TimedActivity Sprint (Time 10)
                       , WeightedActivity Barbell (Pounds 100)
                       ]

-- I then want to apply functions to generate summaries and
-- reports from those activities, i.e.:
extractLifts :: (Activity x) => [x] -> [WeightedActivity]
extractTimes :: (Activity x) => [x] -> [TimedActivity]
4

4 回答 4

14

查看http://www.haskell.org/haskellwiki/Heterogenous_collections了解不同的技术。

于 2013-08-30T04:52:08.020 回答
8

对于您的具体示例,您可以使用 anEither来统一同一列表中的两种类型:

both :: [Either TimedActivity WeightedActivity]
both = [ Left $ TimedActivity Sprint (Time 10)
       , Right $ WeightedActivity Barbell (Pounds 100)
       ]

extractLifts :: [Either TimedActivity WeightedActivity] -> [WeightedActivity]
extractLifts = rights

extractTimes :: [Either TimedActivity WeightedActivity] -> [TimedActivity]
extractTimes = lefts

对于两种以上的类型,只需定义自己的抽象数据类型来统一它们:

data Multiple = Case1 Type1 | Case2 Type2 | ...

...以及像这样的提取功能:

extractCase1 :: [Multiple] -> [Type1]
extractCase1 ms = [t1 | Case1 t1 <- ms]
于 2013-08-30T05:05:52.133 回答
3

简短的回答是,如果您想要多态列表,请使用 Python。

长的答案是 Haskell 是故意设计成这样做的。有一些方法可以拥有完全多态的“列表”,但它们很难使用并且效率较低。您将无法对它们使用普通的列表方法,因为它们不是该[a]类型。如果你想组合两种数据类型,Either类型很方便,并且有很多内置函数,但是你添加的类型越多,你的类型签名就越不方便。一个好的经验法则是,如果您尝试构建多态列表,那么您做错了什么。Haskell 有很好的方法用代数数据类型封装类型。

这个特定代码的问题在于,虽然WeightedActivityTimedActivity都是 的实例Activity,但列表仍然必须包含 的单个实例Activity。类型列表Activity a => [a]并不是说你可以混合不同的活动,而是说列表的所有成员,都是Activitys,都是相同的类型Activity您不能拥有Ints 和的列表Doubles,因为它们是不同的类型,即使它们都有Num实例。

相反,您可以将TimedActivityWeightedActivity和组合Activity成一个数据类型

data Activity
    = TimedActivity Running Time
    | WeightedActivity Lifting Pounds
    deriving (Show)

然后,如果您有新的活动要添加,您可以简单地将它们添加到Activity数据类型中。那么你的extract函数很容易用模式匹配来编写。

于 2013-08-30T04:51:32.990 回答
0

extractLifts :: (Activity x) => [x] -> [WeightedActivity]

这一行表示您正在尝试构建一个可以匹配列表中单个元素类型的函数(可能过滤我们的其他类型)。我不认为你可以做到这一点而不去hacky。

(我想,那条线的意思是extractLifts :: [(Activity x) => x] -> [WeightedActivity]——细微的区别是你的版本为整个列表“选择类型 x”一次,所以列表中的所有元素都是相同类型的,相同的 Activity 实例,而第二个版本将分别为每个元素选择类型)

于 2013-08-30T07:10:56.110 回答