I'm learning Haskell by jumping straight into OpenGL and I can't seem to decipher this code:
display :: DisplayCallback
display = do
let color3f r g b = color $ Color3 r g (b :: GLfloat)
vertex3f x y z = vertex $ Vertex3 x y (z :: GLfloat)
clear [ColorBuffer]
renderPrimitive Quads $ do
color3f 1 0 0
vertex3f 0 0 0
vertex3f 0 0.2 0
vertex3f 0.2 0.2 0
vertex3f 0.2 0 0
color3f 0 1 0
vertex3f 0 0 0
vertex3f 0 (-0.2) 0
vertex3f 0.2 (-0.2) 0
vertex3f 0.2 0 0
color3f 0 0 1
vertex3f 0 0 0
vertex3f 0 (-0.2) 0
vertex3f (-0.2) (-0.2) 0
vertex3f (-0.2) 0 0
color3f 1 0 1
vertex3f 0 0 0
vertex3f 0 0.2 0
vertex3f (-0.2) 0.2 0
vertex3f (-0.2) 0 0
flush
My understanding so far:
display
is a function. QUESTION: What is DisplayCallback? do
indicates chaining of computations, something to do with IO monads color3f
and vertex3f
are local functions with three arguments defined within do
using the let
keyword color
and vertex
is an openGL wrapper function for glColor*
and glVertex*
. Now this is where it gets confusing:
Color3
and Vertex3
appears to be some sort of data structure consisting of three arguments. QUESTION: Why is it necessary to use a data structure here? Why the designers of the API chose to use it here?
color3f
function calls color
function and passes in single argument - data structure Color3
with data rg b. For some reason data type here is specified for only the last argument (b :: GLfloat)
QUESTION: Why is the data type specified only for the last argument, why is it specified here at all?
QUESTION: Why is the dollar sign used when calling the color
function color $ Color3 rg (b :: GLfloat)
?
Continuing on:
clear
is wrapper for opengl glClear
and takes as an argument a list, in this case containing only a single element [ColorBuffer]
. renderPrimitive
appears to be wrapper function for opengl glBegin
, Quads
appears to be datatype. QUESTION: What happens next? What does $ do
indicate? (series of computations? something, something IO monads?). flush
is wrapper for glFlush
. 1 . DisplayCallback
is a type synonym for IO ()
. You can use hoogle to look things up, or type :i DisplayCallback
in ghci.
The reason that IO ()
is given a special name is because GLUT
is based on callbacks: functions are registered to handle a specific event, such as displaying, input events, resize events, etc. For the full list, see the docs . You don't need the type synonyms, obviously, but they are provided for clarity and better communication.
2 . "I assume that color and vertex is an openGL wrapper function for glColor* and glVertex*." Not quite - OpenGL
is a wrapper around the more basic OpenGLRaw
, which is a 1:1 mapping of the c opengl library to haskell. vertex
and color
are slightly more sophisticated that glColor
and glVertex
, but you can probably assume for most uses they are the same.
More specifically, vertex
is a member of the Vertex
class which has 4 instances Vertex4
, Vertex3
, and Vertex2
.
3 . Color3
is defined as data Color3 a = Color3 !a !a !a
. The exclamation indicates that the fields are strict. . Why is this necessary? Well, they could have easily used (a,a,a) -> IO ()
or a -> a -> a -> IO ()
but then a function taking a color is indistinguishable from one taking a vector, which are "ideologically" different objects, even if they are represented by precisely the same data (Vertex is data Vertex3 a = Vertex3 !a !a !a
). So you don't want to be able to pass a vertex to a function requiring a color. Also, these datatypes give better performance, ideally, due to the strictness.
4 . Why is the type specified at all? Short answer, is the type system requires it. The type of a literal is Num a => a
which is too polymorphic for the datatype, which requires a concrete type. So you pick said concrete type by using a type annotation.
Why is it only required in one place? The compiler can infer the type of the other fields. Look back to the data type decleration - all three fields must have the same type. If one field is annotated, the rest are trivially inferred. You can also write Color3 r (g :: GLfloat) b
or Color3 (r :: GLfloat) gb
or give the function vertex3f
a type signature.
5 . The $
is just function application with low precedence, it is defined as f $ a = fa; infixr $ 0
f $ a = fa; infixr $ 0
. You can also write color (Color3 rg (b :: GLfloat))
so it is purely a matter of style here.
6 . Perhaps the docs will again best explain what renderPrimitive
is doing. But the short version is this: instead of writing things inside of a glBegin
- glEnd
block, you write it inside of renderPrimitive
. You don't need to write glEnd
because this is hidden inside of renderPrimitive
. So your code is equivalent to:
glBegin (GL_QUADS);
// All the stuff inside goes here ...
glEnd ();
The OpenGL
does some magic in order to bring exceptions from c into the haskell universe properly, but you don't really have to worry about that.
Final comment: If you plan to use haskell for graphics, the writing actual openGL code is probably not the best idea. After all, if you are going to use openGL, why not just use c? There are a whole myriad of graphics libraries out there. You should browse hackage for a package that is right for you. I'm afraid I can't recommend anything as I'm not familiar with what packages are available.
I don't know the actual library in question, but I think I can answer a few of your points.
I would expect that DisplayCallback
is merely an alias to some more complicated type signature. If you open up GHCi and import the necessary modules, you should be able to say
:i DisplayCallback
and it'll tell you what it refers to. Then at least you'll see the definition, even if that doesn't necessarily tell you what it's for .
The do
notation isn't just for the IO monad, it's for any monad . I wouldn't be surprised if OpenGL drawing operations happen in their own specialised monad.
Why is GLFloat
specified? I imagine that Color3
and Vector3
are defined to hold any type of ordinate. We want it to be GLFloat
, hence the type signature. Why is it only on one of the coordinates? I would imagine that the definition of Color3
requires all ordinates to have the same type, so specifying it for one automatically causes the other two to have the same type.
Why does Color3
even exist at all? Why can't we just call color
with three inputs? Well, that's an API design choice, but again I wouldn't be surprised if the definition of Color3
and Vector3
allows you to do arithmetic on entire vectors or colors. So by putting coordinates into a vector, you get to treat them as a single unit, do arithmetic on them, easily store them in lists, etc. If you're doing all that, you don't really want to have to unpack them all again in order to actually call OpenGL functions.
What is the dollar sign for? Well, if I write
color Color3 r g b
this means that I'm calling color
, passing it four arguments: Color3
, r
, g
and b
. That isn't what we want. What we want is
color (Color3 r g b)
That is, calling color
with one argument. You can do that using brackets, or you can use $
instead. It's a bit like the Unix pipe, in that it lets you turn
func3 (func2 (func1 x))
into
func3 $ func2 $ func1 $ x
If you have a lot of functions, it can be annoying to get the right number of closing brackets. For just one function call, it's a matter of taste.
The renderPrimitive
function is taking two arguments. One of them is Quads
(whatever that is), and the other is the entire do-block . So you're passing all of that code as an argument to the renderPrimitive
function (which presumably executes it somehow).
Hopefully this gives you some enlightenment.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.