Can "overloading" via FlexibleInstances return different types, or match on typeclasses?

时间:2016-03-02 10:55:56

标签: haskell overloading

I'm curious about what kind of "overloading" can be accomplished in Haskell's type classes via "FlexibleInstances".

As a simple test, here is a case of an AdjusterType datatype. It defines an adjust operation that will add a different amount to its value based on whether it contains an Integer or a Double:

{-# LANGUAGE FlexibleInstances #-}

class Adjustable a where
    adjust :: a -> Double

data AdjusterType a = Adjuster a
    deriving Show

instance Adjustable (AdjusterType Integer) where
    adjust (Adjuster a) = fromIntegral (a + 20)

instance Adjustable (AdjusterType Double) where
    adjust (Adjuster a) = a + 0.04

That much works as expected:

Prelude> adjust (Adjuster (1000 :: Integer))
1020.0

Prelude> adjust (Adjuster (3 :: Double))
3.04

Is it possible to make the Integer version of adjust return an Integer, and the Double version return a Double?

Generalizing the signature of adjust and removing the fromIntegral on the integer case doesn't work:

class Adjustable a where
    adjust :: Num n => a -> n

instance Adjustable (AdjusterType Integer) where
    adjust (Adjuster a) = a + 20

This produces an error saying that "n" is a rigid type variable that doesn't match Integer:

Couldn't match expected type ‘n’ with actual type ‘Integer’
    ‘n’ is a rigid type variable bound by
       the type signature for adjust :: Num n => AdjusterType Integer -> n
Relevant bindings include
    adjust :: AdjusterType Integer -> n
In the first argument of ‘(+)’, namely ‘a’
In the expression: a + 20

What type was it expecting here that Integer isn't matching...or would no type actually work and it's just a weird error message? (n is lowercase, so presumably it knows it's not a datatype)

Type constraints in the instance specifications also don't appear to participate in the matching resolution:

instance Integral i => Adjustable (AdjusterType i) where
    adjust (Adjuster a) = fromIntegral (a + 20)

instance RealFloat r => Adjustable (AdjusterType r) where
    adjust (Adjuster a) = a + 0.04

So these act like duplicates, as if they were both Adjustable (AdjusterType x)). The constraint only applies after the resolution is done.

Is there any way to provide an overloaded behavior like above to a type class, or must it always be to a specific instance?

2 个答案:

答案 0 :(得分:3)

  

是否可以使整数版本的adjust返回一个Integer,Double版本返回一个Double?

您可以让Adjustable类型类接受两个类型参数而不是一个,因此它会知道AdjusterType中的内容:

{-# LANGUAGE MultiParamTypeClasses #-}

class Adjustable f a where
    adjust :: f a -> a

那么实例应该是:

instance Adjustable AdjusterType Int where
    adjust (Adjuster a) = a + 20

instance Adjustable AdjusterType Double where
    adjust (Adjuster a) = a + 0.04

ghci的一些结果:

> :set +t

> adjust (Adjuster (100 :: Int))
< 120
< it :: Int
> adjust (Adjuster (100 :: Double))
< 100.04
< it :: Double
  

在这里期望Integer不匹配的类型......或者没有类型实际工作,这只是一个奇怪的错误消息?

adjust的返回类型是forall n . Num n => n类型,是具有单个约束Num的多态类型,因此返回具体类型的函数不会键入check。使用fromIntegral包裹您的功能将解决自fromIntegral :: (Integral a, Num b) => a -> b以来的问题。

  

有没有办法向类型类提供上面的重载行为,或者它必须始终是特定的实例吗?

如果您希望函数对每个不同类型的行为都不同,是的,您必须为每个类型添加一个实例。您可以通过限制类的类型参数来添加一些默认行为:

{-# LANGUAGE DeriveFunctor         #-}
{-# LANGUAGE MultiParamTypeClasses #-}

class Extract f where
  extract :: f a -> a

class (Extract f, Functor f, Num a) => Adjustable f a where
  adjust :: f a -> a
  adjust = extract . fmap (+20)

data AdjusterType a = Adjuster a
  deriving (Functor)

instance Extract AdjusterType where
  extract (Adjuster a) = a

instance Adjustable AdjusterType Int where
-- don't have to write any code here

答案 1 :(得分:2)

使用类型系列的解决方案,特别是相关的数据类型,如下所示:

{-# LANGUAGE TypeFamilies, FlexibleInstances #-}

class Adjustable a where
    type Elem a :: *
    adjust :: a -> Elem a


data AdjusterType a = Adjuster a
    deriving (Show)


instance Adjustable (AdjusterType Integer) where
    type Elem (AdjusterType Integer) = Integer

    adjust (Adjuster a) = a + 20

instance Adjustable (AdjusterType Double) where
    type Elem (AdjusterType Double) = Double

    adjust (Adjuster a) = a + 0.04

main = do
    let x = Adjuster 1 :: AdjusterType Integer
        y = Adjuster 1 :: AdjusterType Double
    print $ adjust x
    print $ adjust y

它编译并输出为:

21
1.04
相关问题