Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding type variables that only appear in the return type

I'm having some trouble understanding how to understand and use type variables that only appear in the return type of a function.

I'm trying to use diagrams-cairo to compare two diagrams, pixel by pixel. The renderToList function has the type:

renderToList :: (Ord a, Floating a) => Int -> Int -> Diagram Cairo R2 -> IO [[AlphaColour a]]

Returning a list of lists of AlphaColour a. Bearing in mind that a is (Ord a, Floating a), I figured I could use mathematical and comparison operations on these AlphaColour a values:

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)

However I'm getting the ominous compile error

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;
             .... }

Which I understand as the compiler wanting to know the full type of the renderToList calls.

But what I don't understand is:

  • Why does the compiler need to know the full type? I think I'm only using operations available to Ord and Floating instances.
  • If I do need to provide a type, where exactly in the code would I define this type.
  • How can I even know what the full concrete type returned from renderToList is?

I feel I'm missing something fundamental with the way this code is written, any help would be greatly appreciated.

like image 965
Noel M Avatar asked Aug 09 '14 08:08

Noel M


People also ask

What do you understand by return type of a function?

The result of a function is called its return value and the data type of the return value is called the return type. Every function declaration and definition must specify a return type, whether or not it actually returns a value.

How do you show a return type in Python?

Note: The Python interpreter doesn't display None . So, to show a return value of None in an interactive session, you need to explicitly use print() .

What is a return type Python?

The Python return keyword exits a function and instructs Python to continue executing the main program. The return keyword can send a value back to the main program. A value could be a string, a tuple, or any other object. This is useful because it allows us to process data within a function.

What is return type and data type?

In computer programming, the return type (or result type) defines and constrains the data type of the value returned from a subroutine or method. In many programming languages (especially statically-typed programming languages such as C, C++, Java) the return type must be explicitly specified when declaring a function.


1 Answers

Type variables that only appear in a return type are generally fine, because the Hindley-Milner algorithm that's at the core of Haskell's type inference is two-way: both the way an value is generated and the way that it is used go into determining what concrete type it should have.

Often the right value for a type variable in the return type will be determined by context, for example if you have

data Foo = Foo Int

and then you write

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

then despite read having type Read a => String -> a, there'll be no problem, because it'll be clear to the compiler that the return type of read needs to be Int in this context.

However here your type variable is fundamentally ambiguous: you are generating it with renderToList, doing something more to it with imgDiff, and then finally "consuming" it with < which has type a -> a -> Bool - the way the result of < is used can't help determine what a should be.

So there's no context anywhere for the compiler to work out what type should actually be used. Even though only operations from Floating and Ord are needed, those operations have concrete implementations on each type, and the values of each type also have their own concrete representations. So the compiler really has to choose one type.

You can fix this quite simply by just adding a type signature. In this case adding one to the line that sets up baseAlphaColours should do it, because all the other uses are constrained by the signatures of the other functions:

For example to choose Float, you could change the relevant line to:

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

In this case the requirements are actually slightly more complicated than just Floating and Ord. So Float may not work as it doesn't normally have a Monoid instance. If you get an error about "no instance for Monoid Float", you may need to use a different type.

If you expect the images to be composed by point-wise addition of individual pixels, then the right type to use would be something like Sum Float, where Sum is obtained from Data.Monoid. So something like:

baseAlphaColours <- renderToList 400 400 base :: IO [[AlphaColour (Sum Float)]]
like image 70
GS - Apologise to Monica Avatar answered Sep 28 '22 13:09

GS - Apologise to Monica