Rust泛型关联类型(GAT)的Haskell等价实现及报错解决咨询
原Rust代码
trait Foo { type Bar<T>; fn foo(x: String) -> Self::Bar<String>; }
用户尝试的Haskell实现
class Foo f where type Bar f :: * -> * foo :: String -> Bar f String
报错信息
app/Main.hs:83:5: error:
• Couldn't match type: Bar f0
with: Bar f
Expected: String -> Bar f String
Actual: String -> Bar f0 String
NB: ‘Bar’ is a non-injective type family
The type variable ‘f0’ is ambiguous
• In the ambiguity check for ‘foo’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the class method:
foo :: forall f. Foo f => String -> Bar f String
In the class declaration for ‘Foo’
|
83 | foo :: String -> Bar f String
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
错误根源
Haskell的类型检查器无法通过Bar f String反向推导f的类型——因为类型族Bar是非内射的:不同的f可能对应完全相同的Bar f类型构造器。这就导致编译器在处理foo时,无法确定要关联哪个Foo实例,从而抛出歧义错误。
三种解决方式
1. 启用AllowAmbiguousTypes扩展
这是最贴近Rust原写法的方案:允许类中存在歧义类型签名,在调用时通过**类型应用(TypeApplications)**明确指定f的类型。
修改后的代码:
{-# LANGUAGE TypeFamilies, AllowAmbiguousTypes #-} class Foo f where type Bar f :: * -> * foo :: String -> Bar f String
定义实例示例:
data MyFoo instance Foo MyFoo where type Bar MyFoo = Maybe foo s = Just s
调用时通过@指定类型:
main = print $ foo @MyFoo "hello" -- 输出 Just "hello"
2. 用Proxy传递类型见证
如果不想启用歧义类型扩展,可以给foo添加Proxy f参数,让编译器通过参数明确f的类型:
{-# LANGUAGE TypeFamilies, FlexibleContexts #-} import Data.Proxy class Foo f where type Bar f :: * -> * foo :: Proxy f -> String -> Bar f String
调用时传入Proxy实例:
main = print $ foo (Proxy :: Proxy MyFoo) "hello"
3. 标记类型族为内射(适用有限场景)
如果能保证所有Foo实例的Bar f都是唯一的(即不同f对应不同Bar f),可以启用InjectiveTypeFamilies扩展,让编译器知道Bar是内射的,从而反向推导f:
{-# LANGUAGE TypeFamilies, InjectiveTypeFamilies #-} class Foo f where type Bar f :: * -> * type instance Injective Bar foo :: String -> Bar f String
这种方式要求所有Bar实例必须满足内射性,仅适合特定场景。
内容的提问来源于stack exchange,提问作者Jw C

