Question

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:

  1. display is a function. QUESTION: What is DisplayCallback?
  2. do indicates chaining of computations, something to do with IO monads
  3. color3f and vertex3f are local functions with three arguments defined within do using the let keyword
  4. I assume that 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 r g 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 r g (b :: GLfloat)?

Continuing on:

  1. clear is wrapper for opengl glClear and takes as an argument a list, in this case containing only a single element [ColorBuffer].
  2. 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?).
  3. flush is wrapper for glFlush.
Was it helpful?

Solution

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) g b or give the function vertex3f a type signature.

5 . The $ is just function application with low precedence, it is defined as f $ a = f a; infixr $ 0. You can also write color (Color3 r g (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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top