Understanding Haskell Type From Old Exam
I am studying Haskell exam and I am a little stuck on this question:
My first thought was to write something like this
paste :: Region -> Image a -> Image a -> Image a
paste (Image bol) img1 img2 = if bol
then -- do the pasting
else -- well do nothing
But I don't know how to do it. Can anyone point me in the right direction?
source to share
While this is unattractive when you're actually sitting on an exam, here's the "teach a man to bait fish" answer: start without defining any outcome at all, and let the compiler (should be GHC≥7.8) comment on it. Note that the stub is wrong: if Image
- is just type
-def, you don't need a value constructor Image
to match the pattern on it.
newtype Image a = Image (Position -> a)
-- it should be `newtype` instead of `type`, else there
-- won't be an explicit `Image` value constructor
type Position = (Float, Float)
type Region = Image Bool
paste :: Region -> Image a -> Image a -> Image a
paste (Image bol) img1 img2 = _
GHC will return with
/tmp/wtmpf-file6644.hs:8:31:
Found hole ‘_’ with type: Image a
Where: ‘a’ is a rigid type variable bound by
the type signature for
paste :: Region -> Image a -> Image a -> Image a
...
Ok, this is not surprising: we already know that the result must be an image. Now Image
there is only one constructor for, which is Image
why we know for sure that we need to build such an image. Still don't know anything about internal implementation:
paste (Image bol) img1 img2 = Image _
gives
/tmp/wtmpf-file6644.hs:8:37:
Found hole ‘_’ with type: Position -> a
Where: ‘a’ is a rigid type variable bound by
the type signature for
paste :: Region -> Image a -> Image a -> Image a
at /tmp/wtmpf-file6644.hs:7:10
Aha, so in Image
hides an extra function argument, hidden! Let's also the pattern match this:
paste (Image bol) img1 img2 = Image $ \pos -> _
/tmp/wtmpf-file6644.hs:8:47:
Found hole ‘_’ with type: a
...
Ok, a
completely opaque, so now we know for sure that we can't do anything without additional information. Fortunately, we have additional information in the form of arguments. How the GHC will tell you:
...
Relevant bindings include
pos :: Position (bound at /tmp/wtmpf-file6644.hs:8:40)
img2 :: Image a (bound at /tmp/wtmpf-file6644.hs:8:24)
img1 :: Image a (bound at /tmp/wtmpf-file6644.hs:8:19)
bol :: Position -> Bool (bound at /tmp/wtmpf-file6644.hs:8:14)
We can't deal with it ... which is good, because it makes it obvious what needs to be done: we can feed pos
as an argument bol
. The result then has a type Bool
(as opposed to itself bol
), so this is now a good candidate for a switch if
.
paste (Image bol) img1 img2 = Image $ \pos -> if bol pos
then _
else _
...
Found hole ‘_’ with type: a
...
seen this before, so we need more information again. Take a look back at these arguments: Image a
can still be mapped to a drawing
paste (Image bol) (Image img1) (Image img2)
= Image $ \pos -> if bol pos
then _
else _
Now he says:
Relevant bindings include
pos :: Position (bound at /tmp/wtmpf-file6644.hs:9:14)
img2 :: Position -> a (bound at /tmp/wtmpf-file6644.hs:8:39)
img1 :: Position -> a (bound at /tmp/wtmpf-file6644.hs:8:26)
bol :: Position -> Bool (bound at /tmp/wtmpf-file6644.hs:8:14)
Aha, so img1
they img2
can bring value a
as needed, we just need to feed them first Position
. Well, we still have it pos
, so it's obvious what to do:
paste (Image bol) (Image img1) (Image img2)
= Image $ \pos -> if bol pos
then img1 pos
else img2 pos
... or with two images swapping (think about it yourself). But there are really no other definitions you can write using just the given information (and all of that!), So this typed hole routine is a pretty reliable way to implement a function.
source to share
Good. You got the screen coordinates, represented by the rectangle [0.1] × [0.1]. Area is a Boolean function over this rectangle that returns true if and only if the coordinate is in this area. Then you have two images, img1
and img2
, which are mappings from coordinates to pixels (or theoretically to another).
You want to return a new image that is equal img1
for the inside coordinates reg
or img2
for the outside coordinates reg
. Images are mappings from coordinates to pixels, so you are indeed returning a function.
Type Region -> Image a -> Image a -> Image a
, but remember that Image a
there is Position -> a
a Region
- Image Bool
, so it really is (Position -> Bool) -> (Position -> a) -> (Position -> a ) -> Position -> a
. We'll need this final parameter Position
before -> a
and provide it as an argument for the three parameters of the function.
Thus,
paste :: Region -> Image a -> Image a -> Image a
-- Is really: (Position->Bool) -> (Position->a) -> (Position->a) -> Position -> a
paste reg overlay bg coords | reg coords == True = overlay coords
| otherwise = bg coords
Longer version with test case:
module Image where
type Image a = Position -> a
type Position = (Float, Float) -- [0,1]×[0,1]
data Color = RGB Int Int Int -- Pixels have values in [0,255]×[0,255]×[0,255].
deriving Show
-- Not efficient for real-world use, but will do for now.
type Region = Image Bool
type ColorImage = Image Color
paste :: Region -> Image a -> Image a -> Image a
-- Is really: (Position->Bool) -> (Position->a) -> (Position->a) -> Position -> a
paste reg overlay bg coords | reg coords == True = overlay coords
| otherwise = bg coords
allRed :: ColorImage
allRed _ = RGB 255 0 0
allWhite :: ColorImage
allWhite _ = RGB 255 255 255
unitCircle :: Region
unitCircle (x,y) = sqrt (x'*x' + y'*y') <= 0.5
where x' = x - 0.5
y' = y - 0.5
redCircleOnWhite :: ColorImage
redCircleOnWhite = paste unitCircle allRed allWhite
You can test redCircleOnWhite
in the REPL.
source to share