我在理解如何理解和使用仅出现在函数返回类型中的类型变量时遇到了一些困难。
我正在尝试使用 diagrams-cairo逐个像素地比较两个图表。 renderToList函数的类型为:
renderToList :: (Ord a, Floating a) => Int -> Int -> Diagram Cairo R2 -> IO [[AlphaColour a]]
返回AlphaColour a
列表的列表。记住 a
是 (Ord a, Floating a)
,我想我可以对这些 AlphaColour a
值使用数学和比较运算:
import Diagrams.Prelude
import Diagrams.Backend.Cairo
import Diagrams.Backend.Cairo.List
import Data.Colour
import Data.Colour.SRGB
import Data.Foldable (fold)
import Data.Monoid
cmp :: Diagram Cairo R2 -> Diagram Cairo R2 -> Diagram Cairo R2 -> IO Bool
cmp base img1 img2 = do
baseAlphaColours <- renderToList 400 400 base
img1AlphaColours <- renderToList 400 400 img1
img2AlphaColours <- renderToList 400 400 img2
return $ (imgDiff baseAlphaColours img1AlphaColours) < (imgDiff baseAlphaColours img2AlphaColours)
imgDiff :: (Ord a, Monoid a, Floating a) => [[AlphaColour a]] -> [[AlphaColour a]] -> a
imgDiff img1 img2 = fold $ zipWith diffPix (concat img1) (concat img2)
diffPix :: (Ord a, Floating a) => AlphaColour a -> AlphaColour a -> a
diffPix a1 a2 = (diffRed * diffRed) - (diffGreen * diffGreen) - (diffBlue * diffBlue)
where red a = channelRed $ toSRGB (a `over` black)
green a = channelGreen $ toSRGB (a `over` black)
blue a = channelBlue $ toSRGB (a `over` black)
diffRed = (red a1) - (red a2)
diffGreen = (green a1) - (green a2)
diffBlue = (blue a1) - (blue a2)
但是我遇到了不祥的编译错误
Ambiguous type variable `a0' in the constraints:
(Floating a0)
arising from a use of `renderToList' at newcompare.hs:11:37-48
(Ord a0)
arising from a use of `renderToList' at newcompare.hs:11:37-48
(Monoid a0)
arising from a use of `imgDiff' at newcompare.hs:14:27-33
Probable fix: add a type signature that fixes these type variable(s)
In a stmt of a 'do' block:
baseAlphaColours <- renderToList 400 400 base
In the expression:
do { baseAlphaColours <- renderToList 400 400 base;
img1AlphaColours <- renderToList 400 400 img1;
img2AlphaColours <- renderToList 400 400 img2;
return
$ (imgDiff baseAlphaColours img1AlphaColours)
< (imgDiff baseAlphaColours img2AlphaColours) }
In an equation for `cmp':
cmp base img1 img2
= do { baseAlphaColours <- renderToList 400 400 base;
img1AlphaColours <- renderToList 400 400 img1;
img2AlphaColours <- renderToList 400 400 img2;
.... }
我理解为编译器想要知道 renderToList
调用的完整类型。
但我不明白的是:
- 为什么编译器需要知道完整类型?我认为我仅使用可用于
Ord
和Floating
实例的操作。 - 如果我确实需要提供类型,我将在代码中的具体位置定义此类型。
- 我怎么知道从
renderToList
返回的完整具体类型是什么?
我觉得我在编写这段代码的方式中遗漏了一些基本的东西,任何帮助将不胜感激。
最佳答案
仅出现在返回类型中的类型变量通常没问题,因为作为 Haskell 类型推断核心的 Hindley-Milner 算法是双向的:生成值的方式和它的使用方式决定了它应该具有什么具体类型。
返回类型中类型变量的正确值通常由上下文决定,例如,如果您有
data Foo = Foo Int
然后你写
mkFoo :: String -> Foo
mkFoo x = Foo (read x)
尽管read
有类型Read a => String -> a
,不会有问题,因为编译器会清楚 read
的返回类型需要是Int
在此背景下。
但是这里你的类型变量从根本上来说是不明确的:你用 renderToList
生成它。 ,用 imgDiff
做更多的事情,然后最后用 <
“消耗”它其类型为 a -> a -> Bool
- <
的结果方式用了无法判断什么a
应该是。
因此,编译器没有任何上下文可以确定实际应该使用什么类型。即使仅来自 Floating
的操作和Ord
需要时,这些操作在每种类型上都有具体的实现,并且每种类型的值也有自己的具体表示。所以编译器确实必须选择一种类型。
只需添加类型签名即可非常简单地解决此问题。在本例中,在设置 baseAlphaColours
的行中添加 1应该这样做,因为所有其他用途都受到其他函数签名的限制:
例如选择Float
,您可以将相关行更改为:
baseAlphaColours <- renderToList 400 400 base :: IO [[AlphaColour Float]]
在这种情况下,要求实际上比 Floating
稍微复杂一些。和Ord
。所以Float
可能无法工作,因为它通常没有 Monoid
实例。如果您收到有关“没有 Monoid Float 实例”的错误,您可能需要使用不同的类型。
如果您希望图像由各个像素的逐点相加组成,那么正确使用的类型将类似于 Sum Float
,其中Sum
获取自Data.Monoid
。所以类似:
baseAlphaColours <- renderToList 400 400 base :: IO [[AlphaColour (Sum Float)]]
关于haskell - 了解仅出现在返回类型中的类型变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25216612/