我正在通过直接进入OpenGL学习Haskell,我似乎无法破译这段代码:
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
到目前为止我的理解:
display
是一个功能。 问题:什么是DisplayCallback?do
表示计算的链接,与IO monads有关color3f
和vertex3f
是本地函数,使用do
关键字在let
中定义了三个参数color
和vertex
是glColor*
和glVertex*
的openGL包装函数。 现在这让人感到困惑:
Color3
和Vertex3
似乎是某种由三个参数组成的数据结构。 问题:为什么有必要在这里使用数据结构?为什么API的设计者选择在这里使用它?
color3f
函数调用color
函数并传入单个参数 - 数据结构Color3
,数据为r g b。由于某种原因,此处的数据类型仅针对最后一个参数(b :: GLfloat)
指定问题:为什么仅为最后一个参数指定数据类型,为什么在此处指定它?
问题:为什么在调用color
函数color $ Color3 r g (b :: GLfloat)
时会使用美元符号?
继续:
clear
是opengl glClear
的包装器,并将列表作为参数,在这种情况下只包含一个元素[ColorBuffer]
。renderPrimitive
似乎是opengl glBegin
的包装函数,Quads
似乎是数据类型。 问题:接下来会发生什么? $ do
表示什么? (一系列计算?某事, IO monads?)。flush
是glFlush
的包装。答案 0 :(得分:6)
1。 DisplayCallback
是IO ()
的类型同义词。您可以使用hoogle查找内容,或在ghci中输入:i DisplayCallback
。
IO ()
被赋予特殊名称的原因是因为GLUT
基于回调:函数被注册以处理特定事件,例如显示,输入事件,调整大小事件等。完整列表,请参阅docs。显然,您不需要类型同义词,但它们是为了清晰和更好的通信而提供的。
2。 “我认为颜色和顶点是glColor *和glVertex *的openGL包装函数。”不完全 - OpenGL
是更基本的OpenGLRaw
的包装器,它是c opengl库到haskell的1:1映射。 vertex
和color
稍微复杂一点glColor
和glVertex
,但您可以假设它们在大多数情况下都是相同的。
更具体地说,vertex
是Vertex
类的成员,其中有4个实例Vertex4
,Vertex3
和Vertex2
。
3。 Color3
定义为data Color3 a = Color3 !a !a !a
。 The exclamation indicates that the fields are strict.。为什么这有必要?好吧,他们可以很容易地使用(a,a,a) -> IO ()
或a -> a -> a -> IO ()
,但是一个颜色的函数与采用矢量的函数无法区分,这是“意识形态上”不同的对象,即使它们由完全相同的表示数据(顶点为data Vertex3 a = Vertex3 !a !a !a
)。因此,您不希望能够将顶点传递给需要颜色的函数。此外,由于严格性,这些数据类型在理想情况下提供了更好的性能。
4。为什么指定类型?简短的回答,是类型系统需要它。文字的类型是Num a => a
,它对于数据类型来说过于多态,这需要具体的类型。因此,您使用类型注释选择所述具体类型。
为什么只在一个地方需要?编译器可以推断其他字段的类型。回顾数据类型decleration - 所有三个字段必须具有相同的类型。如果一个字段被注释,则其余字段被简单推断。您还可以编写Color3 r (g :: GLfloat) b
或Color3 (r :: GLfloat) g b
或为函数vertex3f
提供类型签名。
5。 $
只是具有低优先级的函数应用程序,它被定义为f $ a = f a; infixr $ 0
。你也可以写color (Color3 r g (b :: GLfloat))
所以这里纯粹是一种风格问题。
6。 Perhaps the docs will again best explain what renderPrimitive
is doing.但简短的版本就是这样:不是在glBegin
- glEnd
块内写东西,而是在renderPrimitive
内写。您无需编写glEnd
,因为它隐藏在renderPrimitive
内。所以你的代码相当于:
glBegin (GL_QUADS);
// All the stuff inside goes here ...
glEnd ();
为了将c中的异常正确地带入haskell宇宙,OpenGL
有一些魔力,但你真的不必担心这一点。
最终评论:如果您打算将haskell用于图形,那么编写实际的openGL代码可能不是最好的主意。毕竟,如果你打算使用openGL,为什么不用c?那里有无数的图形库。您应该浏览hackage以获得适合您的包装。我担心我不能推荐任何东西,因为我不熟悉哪些包可用。
答案 1 :(得分:2)
我不知道有问题的实际图书馆,但我想我可以回答你的一些观点。
我希望DisplayCallback
仅仅是一些更复杂的类型签名的别名。如果你打开GHCi并导入必要的模块,你应该可以说
:i DisplayCallback
它会告诉你它的含义。那么至少你会看到这个定义,即使这并不一定能告诉你 。
do
符号不仅适用于IO monad,而适用于任何monad 。如果OpenGL绘图操作发生在他们自己的专用monad中,我不会感到惊讶。
为什么要指定GLFloat
?我想Color3
和Vector3
被定义为包含纵坐标的任何类型。我们希望它是GLFloat
,因此是类型签名。为什么它仅在坐标的一个上?我认为Color3
的定义要求所有纵坐标具有相同的类型,因此将其指定为一个会自动导致其他两个具有相同的类型。
为什么Color3
甚至存在?为什么我们不能只用三个输入调用color
?嗯,这是一个API设计选择,但如果Color3
和Vector3
的定义允许您对整个矢量或颜色进行算术运算,我也不会感到惊讶。因此,通过将坐标放入向量中,您可以将它们视为一个单元,对它们进行算术运算,轻松地将它们存储在列表中等等。如果您正在完成所有这些操作,那么您实际上并不想要将它们解压缩所有这些都是为了实际调用OpenGL函数。
美元符号是什么?好吧,如果我写
color Color3 r g b
这意味着我正在调用color
,传递四个参数:Color3
,r
,g
和b
。那不是我们想要的。我们想要的是
color (Color3 r g b)
即,使用一个参数调用color
。您可以使用括号执行此操作,也可以使用$
代替。它有点像Unix管道,因为它可以让你转向
func3 (func2 (func1 x))
到
func3 $ func2 $ func1 $ x
如果你有 lot 的功能,那么获得正确数量的右括号会很烦人。对于一个函数调用,这是一个品味问题。
renderPrimitive
函数正在使用两个参数。其中一个是Quads
(无论是什么),另一个是整个do-block 。因此,您将所有代码作为参数传递给renderPrimitive
函数(可能以某种方式执行它)。
希望这会给你一些启示。