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?
答案 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