I am trying to learn gtk2hs, an API wich allows haskell programs to use windows, menus, toolbars and graphs.
I want to draw the Mandelbrot set, so I began to write the stuff, but I am stuck at the end, namely when I have to use side effects to draw each point of the Mandelbrot set.
The datas are : I have a canvas (a drawing area) of 300px * 200px, and a function mandelbrot :: Float -> Float -> Bool
, whose output is true if the point is in the mandelbrot set, and false otherwise.
the work to achieve is : for each pixel (width : 0 to 300, height : 0 to 200), transform the coordinates to a range of [-2..2]*[-2..2], call the mandelbrot function, and if the result is true, call a function of the package Cairo which draws the point. (The function is C.rectangle a b 1 1
.)
My try:
example :: Double -> Double -> C.Render ()
example width height = do
setSourceRGB 0 0 0
setLineWidth 1
let
affiche a b = do
if (mandelbrot a b) then
C.rectangle a b 1 1
return()
return ()
colonnes = [0..299]
lignes = [0..199]
in
map (\t -> t/300*4-2) colonnes
map (\t -> t/200*4-2) lignes
map affiche (zip (colonnes,lignes))
stroke -- it displays the changes on the screen
It triggers the error:
- at the second return
:
Error: Parse error: return
thanks
EDIT: Thanks a lot for your answer. I greatly improved my program, but I still have errors.
here is the latest version:
affiche :: Double -> Double -> Render()
affiche a b = when (mandelbrot a b) $ C.rectangle a b 1 1
colonnes = [ t/300.0*4.0-2.0 | t<-[0.0..299.0] ]
lignes = [ t/200.0*4.0-2.0 | t<-[0.0..199.0] ]
example :: Double -> Double -> C.Render ()
example width height = do
setSourceRGB 0 0 0
setLineWidth 1
mapM_ (\ (a, b) -> affiche a b) (zip (colonnes,lignes))
stroke
and the error is:
(all 2 at the line with "mapM_"):
* Couldn't match type ‘[(a0, b0)]’ with ‘(Double, Double)’
Expected type: [b0] -> (Double, Double)
Actual type: [b0] -> [(a0, b0)]
In the second argument of ‘mapM_’, namely
‘(zip (colonnes, lignes))’
In a stmt of a 'do' block:
mapM_ (\ (a, b) -> affiche a b) (zip (colonnes, lignes))
* Couldn't match expected type ‘[a0]’
with actual type ‘([Double], [Double])’
In the first argument of ‘zip’, namely ‘(colonnes, lignes)’
In the second argument of ‘mapM_’, namely
‘(zip (colonnes, lignes))’
In a stmt of a 'do' block:
mapM_ (\ (a, b) -> affiche a b) (zip (colonnes, lignes))
I also have an other question : please confirm me that if a function returns the type "Render()" then all the statements inside it should return a value of this type. thanks
The if
construct in Haskell doesn't work like in other languages: it's actually the equivalent to the ternary conditional operator that's ? :
in C-like languages. That means, if you use if ... then
, you must aways also have an else
branch.
The reason: Haskell isn't imperative; you don't write out what should be done but what the result should be. In an imperative language, you can do nothing, but in Haskell you always need to specify some result.
Now, a monadic do
block of course is basically an imperative embedded language. Here, you can just specify that a local result should be the no-op action, to achieve that nothing is done at that point:
affiche a b = do
if mandelbrot a b then
C.rectangle a b 1 1
return () -- note: `return` isn't needed here
else
return ()
return () -- nor here
A shorter way of writing this would be with the when
combinator, which is basically if-then-else with a return ()
in the else-branch:
affiche a b = when (mandelbrot a b) $ C.rectangle a b 1 1
There's another problem with your code:
in
map (\t -> t/300*4-2) colonnes -- number list
map (\t -> t/200*4-2) lignes -- number list
map affiche (zip (colonnes,lignes)) -- list of `Render ()` actions
stroke -- `Render ()` action
There, you just write out a couple of expressions of completely different types. You can't do that! What is Haskell supposed to do with these expressions?
Well, evidently you want to execute a sequence of actions. Hence you need do
again!
in do
...
But these lists you get from map (\t -> t/300*4-2) colonnes
are not actions at all. You can't execute them, just evaluate them. In this case, apparently you want colonnes
to be the result of mapping that function over the list [0..299]
. Well, then why don't you specify this right away?
colonnes = map (\t -> t/300*4-2) [0..299]
lignes = map (\t -> t/200*4-2) [0..199]
or, why not as a list comprehension
colonnes = [ t/300*4-2 | t<-[0..299] ]
lignes = [ t/200*4-2) t<-[0..199] ]
Finally, you need to map affiche
over the lists. That's in fact a “monadic action map”. The function for this is mapM_
, not map
.
colonnes = [ t/300*4-2 | t<-[0..299] ]
lignes = [ t/200*4-2) t<-[0..199] ]
in
mapM_ affiche $ zip colonnes lignes
stroke
Almost there, but not quite. If the signature of affiche
would take a tuple of two numbers (you get such tuples from zip
), then this would work. However, affiche a b = do
means the function is curried (as is customary in Haskell, in particularly also for zip
!). You can easily undo that though
mapM_ (uncurry affiche) $ zip colonnes lignes
but in this specific case I would actually recommend instead defining
affiche :: (Float,Float) -> C.Render ()
affiche (a,b) = when (mandelbrot a b) $ C.rectangle a b 1 1
because a
and b
really belong together, forming a single coordinate specification.
There's one more problem: you've used Float
numbers here. Well... it doesn't really make sense to do that, in Haskell. At any rate, rectangle
needs Double
as arguments, so either switch both affiche
and mandelbrot
to also take Double
, or convert a
and b
before passing to rectangle
:
affiche :: (Float,Float) -> C.Render ()
affiche (a,b) = when (mandelbrot a b)
$ C.rectangle (realToFrac a) (realToFrac b) 1 1
Oh, and yet another thing: I don't think zip
does the right thing here. But... figure this out yourself...