5

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

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

但我不明白的是:

  • 为什么编译器需要知道完整类型?我想我只使用可用于OrdFloating实例的操作。
  • 如果我确实需要提供一种类型,那么我将在代码中的确切位置定义这种类型。
  • 我怎么知道返回的完整具体类型renderToList是什么?

我觉得我在编写这段代码的方式上遗漏了一些基本的东西,任何帮助都将不胜感激。

4

2 回答 2

8

仅出现在返回类型中的类型变量通常很好,因为作为 Haskell 类型推断核心的 Hindley-Milner 算法是双向的:生成值的方式使用它的方式都决定了什么它应该具有的具体类型。

通常,返回类型中类型变量的正确值将由上下文确定,例如,如果您有

data Foo = Foo Int

然后你写

mkFoo :: String -> Foo
mkFoo x = Foo (read x)

那么尽管read有 type Read a => String -> a,也不会有问题,因为编译器会很清楚,返回类型 ofread需要Int在这个上下文中。

但是,在这里,您的类型变量从根本上是模棱两可的:您正在使用 生成它,使用 对其renderToList进行更多操作imgDiff,然后最后使用<具有类型的“消费”它a -> a -> Bool- 使用结果的方式<无助于确定a应该是什么。

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

您只需添加类型签名即可轻松解决此问题。在这种情况下,在设置的行中添加一个就可以了baseAlphaColours,因为所有其他用途都受到其他函数签名的约束:

例如选择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)]]
于 2014-08-09T08:28:23.967 回答
0

要回答您的最后一个问题:

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

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

于 2014-08-09T08:40:35.073 回答