我正在写一些我认为显然是正确的代码,但似乎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.
答案 0 :(得分:6)
f1
和f2
都使用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
才能从两个FunctionalDependencies
和b
中选择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
,据说不会改变任何东西。
有两种方法可以解决这个问题:
在b
或a
上设置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
是的,这件事非常烦人: ←原来这不是必需的,请参阅josejuan的回答。 a
中的class (Convert a b, Convert b c) => F a b c
类型变量没有范围。您可以在b
扩展名的范围内明确显示ScopedTypeVariables
类型变量,但这需要显式通用限定签名。
这种方法更有意义,因为你仍然可以进行多向转换。
在呼叫网站上明确指定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,对于任何a
和c
,您只能选择一个b
。另一方面,现在调用f
(参见the Tagged
documentation)更加麻烦,尽管这是will become nicer in the future。
† 如果您实际上不需要添加Convert Double Int
个实例,那么我建议您不要这样做。