理解只出现在返回类型中的类型变量



我在理解如何理解和使用只出现在函数返回类型中的类型变量时遇到了一些麻烦。

我正在尝试使用图解-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调用的完整类型。

但我不明白的是:

    为什么编译器需要知道完整的类型?我想我只使用OrdFloating实例可用的操作。
  • 如果我需要提供一个类型,我应该在代码的哪个地方定义这个类型。
  • 我怎么知道从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应该是什么。

所以编译器在任何地方都没有上下文来确定应该实际使用什么类型。即使只需要FloatingOrd的操作,这些操作在每种类型上都有具体的实现,每种类型的值也有自己的具体表示。所以编译器必须选择一种类型。

你可以很简单地通过添加类型签名来解决这个问题。在本例中,在设置baseAlphaColours的行中添加一个应该可以做到这一点,因为所有其他使用都受到其他函数签名的约束:

例如选择Float,您可以将相关行更改为:

baseAlphaColours <- renderToList 400 400 base :: IO [[AlphaColour Float]]

在这种情况下,需求实际上比FloatingOrd稍微复杂一些。所以Float可能不工作,因为它通常没有Monoid实例。如果你得到一个关于"no instance for Monoid Float"的错误,你可能需要使用一个不同的类型。

如果您希望图像由单个像素逐点相加组成,那么正确的类型应该是Sum Float,其中Sum是从Data.Monoid获得的。比如:

baseAlphaColours <- renderToList 400 400 base :: IO [[AlphaColour (Sum Float)]]

回答你最后一个问题:

我怎么知道返回的完整具体类型是什么呢renderToList吗?

由于Haskell类型声明中出现的类型变量意味着函数必须具有您决定用于类型变量的所有类型的所需类型,因此只要满足所有约束(这里是Ord aFloating a),您可以选择类型变量应该具有的具体类型

相关内容

最新更新