具有类型依赖性的多个参数类

时间:2014-04-15 07:55:06

标签: haskell

我试图将我的聚合器类型提升为一个类,但我无法弄清楚如何去做。

我有这个:

data Aggregator a b = Aggregator { aggregate :: [a] -> b }
minMax = Aggregator (\xs -> (minimum xs, maximum))

我想要像:

class Aggregator (g a b) where
   aggregate :: g -> [a] -> b

所以我可以这样做:

data DoubleAggregator a b = DoubleAggregator ([a] -> b) ([a] -> b)

instance Aggregator (DoubleAggregator a b) where
     aggregate (DoubleAggregator f g) as = (f as, g as)

这显然不起作用。我想我需要MultipleParamTypeClassesFamilyTypes,但我无法弄明白。我知道,特别是我不需要上课,只能使用data Aggregator a b ...,但我仍然对如何使用课程感兴趣。那我怎么能这样做呢?

2 个答案:

答案 0 :(得分:4)

除了使用类型系列或功能依赖项(如在user2407038的答案中),另一种方法是使用GADT

{-# LANGUAGE GADTs #-}

class Aggregator g where
    aggregate :: g a b -> [a] -> b


data SingleAggregator a b = SingleAggregator ( [a] -> b )

data DoubleAggregator a b where
    DoubleAggregator :: ([a] -> b) -> ([a] -> b) -> DoubleAggregator a (b, b)


instance Aggregator SingleAggregator where
    aggregate (SingleAggregator f) as = f as

instance Aggregator DoubleAggregator where
    aggregate (DoubleAggregator f g) as = (f as, g as)

这是一个更好还是更差的选择取决于你的确切用例,但我发现它在实践中比类型类扩展更简单。

如果你愿意,GADT方法也允许你完全抛弃类型类,只使用单一的代数类型。

data Aggregator a b where
    SingleAggregator :: ([a] -> b) -> Aggregator a b
    DoubleAggregator :: ([a] -> b) -> ([a] -> b) -> Aggregator a (b, b)

aggregate :: Aggregator a b -> [a] -> b
aggregate (SingleAggregator f)   as = f as
aggregate (DoubleAggregator f g) as = (f as, g as)

然而,可能最接近这个的“Haskell-y”方法是只有一个Aggregator新类型,并使其与Applicative类型类可组合。例如:

import Control.Applicative

newtype Aggregator a b = Aggregator { aggregate :: [a] -> b }

instance Functor (Aggregator a) where
    fmap f (Aggregator g) = Aggregator (f . g)

instance Applicative (Aggregator a) where
    pure = Aggregator . const
    Aggregator f <*> Aggregator x = Aggregator $ f <*> x

现在您可以定义简单的聚合器,例如

minAgg = Aggregator minimum
maxAgg = Aggregator maximum

然后使用Applicative接口

将它们组合成更复杂的聚合器
minMax = liftA2 (,) minAgg maxAgg

答案 1 :(得分:2)

请注意,DoubleAggregator的实例输入效果不好,因为输出为(b,b),但类声明表明它必须为b。因此,您要么想出一种方法将两个b合并为一个,这可能是不可取的,或者使您的输出类型取决于您的聚合器类型:

{-# LANGUAGE TypeFamilies #-}

class Aggregator g where
  type Result x y z 
  aggregate :: g a b -> [a] -> Result g a b

data SingleAggregator a b = SingleAggregator ([a] -> b)
instance Aggregator SingleAggregator where 
  type Result SingleAggregator a b = b
  aggregate (SingleAggregator f) = f 

data DoubleAggregator a b = DoubleAggregator ([a] -> b) ([a] -> b)
instance Aggregator DoubleAggregator where
  type Result DoubleAggregator a b = (b,b)
  aggregate (DoubleAggregator f g) as = (f as, g as)

请注意,该类未提及ab,因为它们都可以自由为任何类型,并且不依赖于聚合器类型。类型Result是三个功能的函数:聚合器的类型和聚合器内的类型。结果可能永远不会取决于输入类型a,在这种情况下,您可以编写如下内容:

class Aggregator g where
  type Result x y 
  aggregate :: g a b -> [a] -> Result g b

....

  type Result SingleAggregator b = b

....

  type Result DoubleAggregator b = (b, b)

您也可以使用MultiParamTypeClassesFunctionalDependancies执行此操作,但它更复杂且类型更难阅读,所以在我看来这是一个更糟糕的解决方案,我只是为了完整性而包含它

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}

class Aggregator g a b r | g a b -> r where
  aggregate :: g a b -> [a] -> r

data SingleAggregator a b = SingleAggregator ([a] -> b)
instance Aggregator SingleAggregator a b b where 
  aggregate (SingleAggregator f) = f 

data DoubleAggregator a b = DoubleAggregator ([a] -> b) ([a] -> b)
instance Aggregator DoubleAggregator a b (b,b) where
  aggregate (DoubleAggregator f g) as = (f as, g as)

主要区别在于功能依赖性:| g a b -> r;这表示某些r可以存在一个唯一的g a b