关于haskell:通过FlexibleInstances”重载”可以返回不同的类型,还是匹配类型类?

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

我很好奇在 Haskell 的类型类中可以通过"FlexibleInstances"实现什么样的"重载"。

作为一个简单的测试,这里是一个 AdjusterType 数据类型的例子。它定义了一个 adjust 操作,该操作将根据它是否包含 Integer 或 Double 来为其值添加不同的数量:

1
2
3
4
5
6
7
8
9
10
11
12
13
{-# 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

效果符合预期:

1
2
3
4
5
Prelude> adjust (Adjuster (1000 :: Integer))
1020.0

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

是否可以让 adjust 的 Integer 版本返回 Integer,而 Double 版本返回 Double?

概括 adjust 的签名并删除整数大小写的 fromIntegral 不起作用:

1
2
3
4
5
class Adjustable a where
    adjust :: Num n => a -> n

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

这会产生一个错误,指出"n"是一个与整数不匹配的刚性类型变量:

1
2
3
4
5
6
7
Couldn't match expected type a€?na€? with actual type a€?Integera€?
    a€?na€? 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 a€?(+)a€?, namely a€?aa€?
In the expression: a + 20

这里期望 Integer 不匹配的类型是什么类型...或者实际上没有类型可以工作,这只是一个奇怪的错误消息? (n 是小写,所以大概它知道它不是数据类型)

实例规范中的类型约束似乎也不参与匹配解析:

1
2
3
4
5
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

所以它们的行为就像是重复的,就好像它们都是 Adjustable (AdjusterType x))。约束仅在解析完成后才适用。

有没有办法为类型类提供像上面这样的重载行为,或者必须始终提供给特定实例?


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

你可以让 Adjustable 类型类接受两个类型参数而不是一个,所以它会知道 AdjusterType:

里面的内容

1
2
3
4
{-# LANGUAGE MultiParamTypeClasses #-}

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

那么实例应该是:

1
2
3
4
5
instance Adjustable AdjusterType Int where
    adjust (Adjuster a) = a + 20

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

还有来自 ghci 的一些结果:

1
2
3
4
5
6
7
8
> :set +t

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

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?

adjust 的返回类型是 forall n . Num n => n 类型,它是具有单个约束 Num 的多态类型,因此返回具体类型的函数不会进行类型检查。用 fromIntegral package你的函数将解决问题,因为 fromIntegral :: (Integral a, Num b) => a -> b.

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{-# 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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{-# 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

编译,输出为:

1
2
21
1.04