MultiParamTypeClasses的类型推断?

时间:2016-03-15 09:48:01

标签: haskell

我正在写一些我认为显然是正确的代码,但似乎GHC并不这么认为:

class Convert a b where
    convert :: a -> b
class (Convert a b, Convert b c) => F a b c where 
    f  :: a -> c
    f = f2 . f1 
      where f2 = convert :: b -> c
            f1 = convert :: a -> b

上面的代码给出了这样的错误信息,所以我想知道GHC在尝试推断出合适的类型时遇到了什么困难,或者我是否需要提供GHC更多的类型信息?

Main.hs:53:18:
    Could not deduce (Convert b2 c2) ac)
      bound by the class declaration for ‘F’ at Main.hs:(50,1)-(54,34)
    Possible fix:
      add (Convert b2 c2) to the context of
        an expression type signature: b2 -> c2
    In the expression: convert :: b -> c
    In an equation for ‘f2’: f2 = convert :: b ->    where
              f2 = convert :: b -> c
              f1 = convert :: a -> b

Main.hs:54:18:
    Could not deduce (Convert a2 b2) arising from a use of ‘convert’
    from the context (F a b c)
      bound by  Possible fix:
      add (Convert a2 b2) to the context of
        an expression type signature: a2 -> b2
    In the expression: convert :: a -> b
    In an equation for ‘f1’: f1 = convert :: a -> b
    In an equation for ‘f’:
        f = f2 . f1
          where
              f2 = convert :: b -> c
              f1 = convert :: a -> b
Failed, modules loaded: none.

2 个答案:

答案 0 :(得分:6)

f1f2都使用b,但这是免费的(不限于f)。 ghci如何确定b

您可以ScopedTypeVariables使用b “链接” f1上的f2类型,b上的class { {1}}约束

{-# LANGUAGE UnicodeSyntax #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
class Convert a b where
    convert :: a →  b
class (Convert a b, Convert b c) ⇒  F a b c | a c → b where -- #1
    f  :: a →  c
    f = f2 . f1
      where f2 = convert :: ∀ b . F a b c ⇒ b →  c -- #2
            f1 = convert :: ∀ b . F a b c ⇒ a →  b -- #3

instance Convert Int String where
  convert = show
instance Convert String Double where
  convert = read
instance F Int String Double where

现在

> f (34 :: Int) :: Double
34.0

没有ScopedTypeVariables#1上的b类型名称,#2和#3都是不同的(同样#2和#3通过{{推断相同) 1}})。 f2 . f1全部(#1,#2和#3)的类型相同。

另一方面,需要ScopedTypeVariables才能从两个FunctionalDependenciesb中选择a,因为c没有关于它的信息。

最后,看看@leftaroundabout响应,如果可以的话,可以使用幻像类型更好地在f约束上指定b类型。

(相关问题How to conciliate / constraint types between two separated expressions

答案 1 :(得分:2)

f的签名不包含任何b。因此,无法指定b应该是什么!但是你需要那些信息;举一个极端的例子,考虑转换 Double - > Int - > Double(显然必须将结果四舍五入)与琐碎的转化Double - > Double - > Double,据说不会改变任何东西。
有两种方法可以解决这个问题:

  1. ba上设置c 功能相关(这些可以从签名中读取) 。您可以在Convert

    中执行此操作
    {-# LANGUAGE FunctionalDependencies #-}
    
    class Convert a b | a -> b where
      convert :: a -> b
    class (Convert a b, Convert b c) => F a b c where 
      f  :: a -> c
      f = convert . convert
    

    ...但是,这意味着每种类型只能转换为另一种类型!对于任何a,您只能指定一个实例Convert a b,否则它不具有功能依赖性。 - 或者,您可以在F

    中执行此操作
    {-# LANGUAGE ScopedTypeVariables, UnicodeSyntax #-}
    
    class Convert a b where
      convert :: a -> b
    class (Convert a b, Convert b c) => F a b c | a c -> b where 
      f  :: a -> c
      f = f'
       where f' :: ∀ a b c . F a b c => a -> c
             f' = f2 . f1
              where f1 = convert :: a -> b
                    f2 = convert :: b -> c
    

    是的,这件事非常烦人:a中的class (Convert a b, Convert b c) => F a b c类型变量没有范围。您可以在b扩展名的范围内明确显示ScopedTypeVariables类型变量,但这需要显式通用限定签名。 ←原来这不是必需的,请参阅josejuan的回答。
    这种方法更有意义,因为你仍然可以进行多向转换。

  2. 呼叫网站上明确指定b。如果您为f提供标记的类型签名,则可以执行此操作:

    import Data.Tagged
    
    class Convert a b where
      convert :: a -> b
    class (Convert a b, Convert b c) => F a b c where 
      f  :: Tagged b (a -> c)
      f = f'
       where f' :: ∀ a b c . F a b c => Tagged b (a -> c)
             f' = Tagged (f2 . f1)
              where f1 = convert :: a -> b
                    f2 = convert :: b -> c
    

    f上有关fundep的优势在于,您现在可以指定您喜欢的任何 instance F a b c。使用fundep,对于任何ac,您只能选择一个b。另一方面,现在调用f(参见the Tagged documentation)更加麻烦,尽管这是will become nicer in the future

  3. 如果您实际上不需要添加Convert Double Int个实例,那么我建议您不要这样做。