如何在Haskell中建模多态列表?

时间:2013-08-30 04:25:07

标签: haskell

我正在尝试在haskell中建模一些多态类型数据。我理解为什么以下代码不起作用,但我希望它说明我正在尝试做什么。我的问题是:使用Haskell对此进行建模的惯用方法是什么? (如果有更好的方法,则不需要保持输入格式相同 - 我没有任何现有代码或数据。)

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 个答案:

答案 0 :(得分:12)

答案 1 :(得分:8)

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

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]

答案 2 :(得分:3)

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

答案很长,Haskell是故意设计的 not 来做到这一点。有多种方式可以获得完全多态的“列表”,但它们很难处理并且效率更低。您将无法对它们使用常规列表方法,因为它们不属于[a]类型。如果要组合两种数据类型,Either类型非常方便,并且具有许多内置函数,但添加的类型越多,类型签名就越不方便。一个好的经验法则是,如果你正在尝试构建多态列表,那么你做错了什么。 Haskell有很好的方法用代数数据类型封装类型。

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

相反,您可以将TimedActivityWeightedActivityActivity合并为单个数据类型

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

然后,如果要添加新活动,只需将它们添加到Activity数据类型即可。然后你的extract函数很容易用模式匹配编写。

答案 3 :(得分:0)

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

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

(我想,这一行应该是extractLifts :: [(Activity x) => x] -> [WeightedActivity] - 细微的区别在于你的版本为整个列表“选择类型x”一次,所以列表中的所有元素都是相同的类型,相同的Activity实例,而第二个版本将单独选择每个元素的类型)