haskell - 类型类默认方法实例化中的模糊类型解析
问题描述
为什么以下代码无法键入?
{-# LANGUAGE AllowAmbiguousTypes, MultiParamTypeClasses #-}
module Main where
class Interface a b c where
get :: a -> [b]
change :: b -> c
changeAll :: a -> [c]
changeAll = map change . get
main = return ()
如果我注释掉 的默认实例化--changeAll = map change . get
,一切似乎都很好。但是,在实例化到位后,我收到此错误:
GHCi, version 8.6.5: http://www.haskell.org/ghc/ :? for help
[1 of 1] Compiling Main ( test.hs, interpreted )
test.hs:10:19: error:
• Could not deduce (Interface a0 b0 c)
arising from a use of ‘change’
from the context: Interface a b c
bound by the class declaration for ‘Interface’ at test.hs:5:7-15
The type variables ‘a0’, ‘b0’ are ambiguous
Relevant bindings include
changeAll :: a -> [c] (bound at test.hs:10:3)
• In the first argument of ‘map’, namely ‘change’
In the first argument of ‘(.)’, namely ‘map change’
In the expression: map change . get
|
10 | changeAll = map change . get
| ^^^^^^
test.hs:10:28: error:
• Could not deduce (Interface a b0 c0) arising from a use of ‘get’
from the context: Interface a b c
bound by the class declaration for ‘Interface’ at test.hs:5:7-15
The type variables ‘b0’, ‘c0’ are ambiguous
Relevant bindings include
changeAll :: a -> [c] (bound at test.hs:10:3)
• In the second argument of ‘(.)’, namely ‘get’
In the expression: map change . get
In an equation for ‘changeAll’: changeAll = map change . get
|
10 | changeAll = map change . get
| ^^^
我在这里遗漏了一些明显的东西吗?
解决方案
你所有的方法都是模棱两可的。
为了更好地说明问题,让我们将示例简化为一种方法:
class C a b c where
get :: a -> [b]
现在假设您有以下实例:
instance C Int String Bool where
get x = [show x]
instance C Int String Char where
get x = ["foo"]
然后想象你正在尝试调用该方法:
s :: [String]
s = get (42 :: Int)
从 的签名中s
,编译器知道b ~ String
。从 的参数get
,编译器就知道了a ~ Int
。但什么是c
?编译器不知道。无处可寻。
可是等等!C
matcha ~ Int
和的两个实例b ~ String
,那么选择哪个?不清楚。没有足够的信息。模糊的。
这正是您尝试调用get
and change
in时发生的情况map change . get
:没有足够的类型信息让编译器了解调用或调用的a
,b
和c
是什么。哦,请记住:这两个调用可能来自不同的实例。没有什么可以说它们必须来自与自身相同的实例。get
change
changeAll
有两种可能的方法来解决这个问题。
首先,您可以使用函数依赖,这是一种说法,为了确定c
,知道a
和就足够了b
:
class C a b c | a b -> c where ...
如果您以这种方式声明类,编译器将拒绝相同a
and的多个实例b
,但不同c
的是,另一方面,它只需知道a
and就可以选择实例b
。
当然,你可以在同一个类上有多个功能依赖。例如,您可以声明知道任何两个变量就足以确定第三个变量:
class C a b c | a b -> c, a c -> b, b c -> a where ...
请记住,对于您的changeAll
函数,即使这三个函数依赖项也不够,因为changeAll
"swallows"的实现b
。也就是说,当它调用 时get
,唯一已知的类型是a
。同样,当它调用 时change
,唯一已知的类型是c
。这意味着,为了使这种“吞咽”b
起作用,它必须由a
单独以及单独确定c
:
class Interface a b c | a -> b, c -> b where ...
当然,这只有在您的程序逻辑确实具有某些变量由其他变量决定的属性时才有可能。如果您确实需要所有变量都是独立的,请继续阅读。
其次,您可以使用以下命令明确告诉编译器必须是什么类型TypeApplications
:
s :: String
s = get @Int @String @Bool 42 -- works
不再有歧义。编译器确切地知道要选择哪个实例,因为您已经明确地告诉过它。
将此应用于您的changeAll
实现:
changeAll :: a -> [c]
changeAll = map (change @a @b @c) . get @a @b @c
(注意:为了能够引用类型变量a
, b
, 和c
这样的函数体,您还需要启用ScopedTypeVariables
)
当然,您在调用自身时也需要这样做changeAll
,因为它的类型签名中也没有足够的信息:
foo = changeAll @Int @String @Bool 42
推荐阅读
- c# - 在二维数组中对具有相同值的相邻条目进行分组
- entity-framework - EF CORE:是否可以在一个数据库中支持两种模型?
- python - 使用 Python 获取通知区域中的程序列表
- wordpress - 印度卢比未显示在发票 PDF WooCommerce 网站中
- powershell - 目标机器上的 TFS PowerShell 任务可以在 x86 模式下执行吗?
- arrays - Json 数据 - 白色控制台 - Xcode 9
- react-native - react-native - 在每次调用 API 之前使用 redux-thunk 中间件检查 jwt 是否过期
- ios - Firebase 云消息传递本地化参数错误
- html - 带有主题标签的锚标签指向外部选项卡
- c# - 无法获取当前用户或授权