bra*_*drn 9 polymorphism haskell pattern-matching
(灵感来自,无法在具有约束条件的多态元组上进行匹配,并基于我对自己答案的后续评论。)
考虑下面的最小示例:
test :: (Show a, Show b) => (a -> String, b -> String)
test = (show,show)
(resX, resY) = test
Run Code Online (Sandbox Code Playgroud)
这将导致以下错误:
• Ambiguous type variable ‘a0’ arising from a use of ‘test’
prevents the constraint ‘(Show a0)’ from being solved.
Relevant bindings include
resX :: a0 -> String (bound at so.hs:25:2)
Probable fix: use a type annotation to specify what ‘a0’ should be.
These potential instances exist:
instance Show Ordering -- Defined in ‘GHC.Show’
instance Show Integer -- Defined in ‘GHC.Show’
instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
...plus 22 others
...plus 17 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the expression: test
In a pattern binding: (resX, resY) = test
|
25 | (resX, resY) = test
| ^^^^
Run Code Online (Sandbox Code Playgroud)
这是有道理的:模式匹配不受限制a或b以任何方式约束,因此它们是模棱两可的。但是如何摆脱这个错误呢?几乎所有通过添加类型签名来解决此问题的尝试都会产生另一个错误。例如:
• Ambiguous type variable ‘a0’ arising from a use of ‘test’
prevents the constraint ‘(Show a0)’ from being solved.
Relevant bindings include
resX :: a0 -> String (bound at so.hs:25:2)
Probable fix: use a type annotation to specify what ‘a0’ should be.
These potential instances exist:
instance Show Ordering -- Defined in ‘GHC.Show’
instance Show Integer -- Defined in ‘GHC.Show’
instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
...plus 22 others
...plus 17 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the expression: test
In a pattern binding: (resX, resY) = test
|
25 | (resX, resY) = test
| ^^^^
Run Code Online (Sandbox Code Playgroud)
那么:如何摆脱歧义错误?为什么上述尝试失败了?
考虑一下代码在运行时的实际工作方式是很有启发性的。类型类具有字典传递语义。优雅的代码
class Foo a where
foo :: a -> Int
instance Foo Int where
foo = id
useFoo :: Foo a => a -> IO ()
useFoo x = print (foo x)
callFoo = useFoo (123 :: Int)
Run Code Online (Sandbox Code Playgroud)
被编译成非经典代码。
data Foo_ a = Foo_ { foo :: a -> Int }
foo_Int :: Foo_ Int
foo_Int = Foo_ { foo = id }
useFoo :: Foo_ a -> a -> IO ()
useFoo dict x = print (foo dict x)
callFoo = useFoo foo_Int 123
Run Code Online (Sandbox Code Playgroud)
“胖箭头” =>在运行时的含义实际上与“草瘦箭头”完全相同->。唯一的区别是=>约束求解器隐式传递了的参数。
因此,让我们Show从这个角度考虑您的示例。如A型test :: (Show a, Show b) => (a -> String, b -> String)的真正含义test :: (Show_ a, Show_ b) -> (a, String, b -> String)。您不能将该函数分解为一对函数(Show_ a -> a -> String, Show_ b -> b -> String)。函数的主体Show_在返回结果之前可能会同时使用这两个字典,因此您必须同时提供这两个字典(a -> String, b -> String)。
在为推断类型时resx,将提供类型检查器resx :: (Show a, Show b) => a -> String-它需要作用域中的两个实例才能进行调用test。显然,这是一个模棱两可的类型。b不会出现在的右侧=>,因此在resx的呼叫站点,永远不会有足够的本地类型信息来成功调用它。
您可能会反对在这种情况下应该能够进行这样的分解。test主体的两半不能使用彼此的Show实例,因为Show的方法仅将其类型参数作为输入,并且输入不在元组的错误一半内。但这是参数化有关运行时行为的复杂论据,仅适用于此特定情况。这不是编译器擅长的愚蠢的语法指导推理。
这是有道理的:该模式匹配不会以任何方式限制 a 或 b,因此它们是模棱两可的。但是我该如何摆脱这个错误呢?
在这里提供类型签名无济于事。的类型test是(Show a, Show b) => (a -> String, b -> String)。基本上你要做的是写:
resX :: Show a => a -> String
resX = fst testRun Code Online (Sandbox Code Playgroud)
然而,问题是,Haskell 应该b从 的签名中填写什么test?你可能会说这无所谓,这里确实无所谓。但是如果你test通过一个类型类定义。该a和b类型可以决定在一起的第一个项目的实施将是什么。例如:
{-# LANGUAGE MultiParamTypeClasses #-}
class (Show a, Show b) => SomeTest a b where
test :: (a -> String, b -> String)
instance SomeTest Bool Bool where
test = (const "foo", const "bar")
instance SomeTest Bool Int where
test = (const "qux", const "bar")Run Code Online (Sandbox Code Playgroud)
这里的第二个类型参数决定了 的第一项的实现test,因此我们不能忽略它。
例如,您可以使用TypeApplications扩展为其他类型参数提供类型(此处Bool):
{-# LANGUAGE TypeApplications -#}
test :: (Show a, Show b) => (a -> String, b -> String)
test = (show, show)
resX :: Show a => a -> String
resX = fst (test @_ @Bool)
resY :: Show a => a -> String
resY = snd (test @Bool)Run Code Online (Sandbox Code Playgroud)