问题不在于无法推断出实例,而是编译器确实无法知道您可能想要的类型。 g
可以产生它要求的任何类型(前提是它有一个 Intermediate
实例),f
可以使用任何这样的类型......但没有人指定哪个.
但这很容易解决:现在只需选择一种类型。当然,它必须是有实例的;例如如果你有
instance Intermediate Char where
f = fromEnum
g = id
然后你可以使用
h :: Char -> Int
h = (f :: Char -> Int) . g
修复类型选择的更简洁方法是使用语法扩展:
{-# LANGUAGE TypeApplications #-}
h = f @Char . g
...或者,为了强调您只是在中间修复类型,
h = f . id @Char . g
,
我认为问题在于有人可能使用了 2 种不同类型,它们是该组合中的中间体实例。
不,问题是 Haskell 不能再从签名中推导出 a
使用什么。假设有两种 Intermediate
类型:
instance Intermediate Char where
# …
instance Intermediate Bool where
# …
现在 h
有两种实现:
h :: Char -> Int
h = f . (g :: Char -> Char)
或:
h :: Char -> Int
h = f . (g :: Char -> Bool)
可以使用无限多的 Intermediate
类型。问题是 Haskell 无法根据类型签名判断使用什么类型。
我们可以给它一个类型提示,但这当然意味着中间类型是固定的。
解决此问题的一个简单方法是使用 asTypeOf :: a -> a -> a
。这基本上是一个 const
函数,但两个参数具有相同的类型。因此,这用于添加使用什么类型的提示,例如:
h :: Intermediate a => a -> Char -> Int
h a x = f (g x `asTypeOf` a)
这里的 value a
参数因此不重要,这是一种“注入”类型的方法,该类型将用作 {{1} 的结果的类型} 和 g
的参数。
如果您以后使用 f
,您可以使用:
h
指定 h (undefined :: Char) 'a'
的类型应为 f
,而 Char -> Char
的类型应为 g
。
就像 @leftroundabout 和 @DanielWagner 说的那样,一个更干净的解决方案是在签名中添加类型变量,而不使用这种虚拟变量:
Char -> Int
然后我们可以将 {-# LANGUAGE AllowAmbiguousTypes,ScopedTypeVariables,TypeApplications #-}
h :: forall a. Intermediate a => Char -> Int
h = f . g @ a
与类型变量一起使用:
h
本文链接:https://www.f2er.com/1222092.html