了解仅出现在返回类型中的类型变量

时间:2014-08-09 08:16:35

标签: haskell type-variables haskell-diagrams

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

我正在尝试使用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返回的完整具体类型是什么?

我觉得我错过了编写代码的基本方法,我们将非常感谢任何帮助。

2 个答案:

答案 0 :(得分:8)

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

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

data Foo = Foo Int

然后你写

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

然后尽管read类型为Read a => String -> a,但没有问题,因为编译器会清楚read的返回类型需要{{1在这种情况下。

但是,这里你的类型变量基本上是不明确的:你用Int生成它,用renderToList做更多的事情,然后最后用imgDiff“消耗”它键入< - 使用a -> a -> Bool的结果的方式无法确定<应该是什么。

因此编译器无论何处都没有上下文来确定实际应该使用哪种类型。尽管只需要来自aFloating的操作,但这些操作在每种类型上都有具体的实现,每种类型的值也有自己的具体表示。所以编译器真的必须选择一种类型。

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

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

Float

在这种情况下,要求实际上比baseAlphaColours <- renderToList 400 400 base :: IO [[AlphaColour Float]] Floating稍微复杂一些。因此,Ord可能无效,因为它通常没有Float实例。如果您收到有关“没有Monoid Float实例”的错误,则可能需要使用其他类型。

如果您希望通过逐点添加单个像素来合成图像,那么使用的正确类型将类似Monoid,其中Sum Float来自Sum 。如下所示:

Data.Monoid

答案 1 :(得分:0)

回答你的上一个问题:

  

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

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