Haskell 多ramTypeClass的类型推断?
我写了一些我认为显然是正确的代码,但GHC似乎不这么认为:Haskell 多ramTypeClass的类型推断?,haskell,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 上面的
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.
f1和f2都使用b,但这是自由的,不受f的约束。ghci如何确定b
可以使用ScopedTypeVariables将f1和f2上的b类型与类上的b约束链接起来
{-# 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
现在
没有ScopedTypeVariables,1、2和3上的b类型名称都是不同的,尽管f2推断出2和3是相同的。f1。对于ScopedTypeVariables,所有1、2和3都是相同的类型
另一方面,从两个给定的a和c中选择b需要函数依赖性,因为f没有关于它的信息
最后,看看@leftaroundabout response,如果可以的话,最好使用phantom类型指定f上的b类型约束
相关问题f的签名不包含任何b。因此,没有办法指定b应该是什么!但是你需要这些信息;对于一个极端的例子,考虑转换双->int>双,这显然需要将结果与微不足道的转换双->双->双,这是不会改变任何东西的。有两种方法可以解决这个问题:
使b在功能上依赖于a或c,这些可以从签名中读取。您可以在Convert中正确执行此操作
…但是,这意味着每种类型只能转换为另一种类型!对于任何a,您只能指定一个实例转换为b,否则它不是函数依赖关系。-或者,你可以用F来做
是的,这是一件非常烦人的事情:类中的类型变量Convert a b,Convert b c=>fa b c没有作用域。您可以在ScopedTypeVariables扩展的作用域中显式地引入b类型变量,但这需要显式的通用限定签名。←这在这里是不必要的,见josejuan的答案。这种方法更有意义,因为您仍然可以进行多路转换
在调用站点显式指定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相比,它的优点是现在可以指定任何您喜欢的fabc实例。使用fundep,对于任何a和c,您只能选择一个b。另一方面,现在调用f-see更麻烦了,尽管如此
†如果您实际上不需要添加Convert Double Int实例,那么我建议您不要这样做。有趣!为什么不在这里显式地将b引入f的作用域?@leftaroundabout FlexibleInstances选择中间实例字符串在这种情况下为什么这里需要forall?我试着在你的源代码中删除它,但它仍然有效。我以前从未使用过forall…通常,当您使用ScopedTypeVariables编写函数时,使用forall将作用域类型变量设置为全身可见。在本例中,主体只是一个表达式。
{-# 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
{-# 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
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