能否通过 FlexibleInstances "overloading" return 不同的类型,或匹配类型类?

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

我很好奇在 Haskell 的类型 classes 中可以通过 "FlexibleInstances".

完成什么样的 "overloading"

作为一个简单的测试,这里是一个 AdjusterType 数据类型的例子。它定义了一个 adjust 操作,该操作将根据它包含的是 Integer 还是 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

达到预期效果:

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

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

是否可以将 adjust return 的 Integer 版本设为 Integer,将 Double 版本 return 设为 Double?

推广 adjust 的签名并删除整数情况下的 fromIntegral 不起作用:

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

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

这会产生一个错误,指出 "n" 是一个与 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

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

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

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))。约束仅在解析完成后适用。

是否有任何方法可以为类型 class 提供上述重载行为,或者它必须始终针对特定实例?

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

你可以让 Adjustable 类型 class 接受两个类型参数而不是一个,这样它就会知道 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

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 的 return 类型是 forall n . Num n => n 类型,一个具有单一约束 Num 的多态类型,所以你的函数 returning a具体类型不会进行类型检查。用 fromIntegral 包装你的函数将解决问题,因为 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?

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

的类型参数来添加一些默认行为
{-# 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

使用类型族(尤其是关联数据类型)的解决方案如下:

{-# 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