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

Noe*_*l M 5 haskell type-variables haskell-diagrams

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

我正在尝试使用diagram-cairo逐个像素地比较两个图表.该renderToList函数的类型:

renderToList :: (Ord a, Floating a) => Int -> Int -> Diagram Cairo R2 -> IO [[AlphaColour a]]
Run Code Online (Sandbox Code Playgroud)

返回列表清单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)
Run Code Online (Sandbox Code Playgroud)

但是我得到了不祥的编译错误

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;
             .... }
Run Code Online (Sandbox Code Playgroud)

我理解为编译器想知道完整类型的renderToList调用.

但我不明白的是:

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

我觉得我错过了编写代码的基本方法,任何帮助都会非常感激.

GS *_*ica 8

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

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

data Foo = Foo Int
Run Code Online (Sandbox Code Playgroud)

然后你写

mkFoo :: String -> Foo
mkFoo x = Foo (read x)
Run Code Online (Sandbox Code Playgroud)

然后尽管read有类型Read a => String -> a,但没有问题,因为编译器清楚地知道返回类型read需要Int在这个上下文中.

然而,这里你的类型变量基本上是模棱两可的:你正在生成它,用它renderToList做更多的事情imgDiff,然后最终"消耗"它的<类型a -> a -> Bool- <使用结果的方式无法确定a应该是什么.

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

您可以通过添加类型签名来简单地解决这个问题.在这种情况下,在设置的行中添加一个baseAlphaColours应该这样做,因为所有其他用途都受到其他函数的签名的约束:

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

baseAlphaColours <- renderToList 400 400 base :: IO [[AlphaColour Float]]
Run Code Online (Sandbox Code Playgroud)

在这种情况下,要求实际上比仅仅Floating和更复杂Ord.所以Float可能无法正常工作,因为它通常没有Monoid实例.如果您收到有关"没有Monoid Float实例"的错误,则可能需要使用其他类型.

如果您希望通过逐点添加单个像素来组合图像,那么使用的正确类型就像是从Sum Float哪里Sum获得的Data.Monoid.所以类似于:

baseAlphaColours <- renderToList 400 400 base :: IO [[AlphaColour (Sum Float)]]
Run Code Online (Sandbox Code Playgroud)