[英]How does this OpenGL Haskell code work?
我正在通過直接進入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
是使用let
關鍵字在do
定義的三個參數的本地函數 color
和vertex
是glColor*
和glVertex*
的openGL包裝函數。 現在這是令人困惑的地方:
Color3
和Vertex3
似乎是由三個參數組成的某種數據結構。 問題:為什么有必要在這里使用數據結構? 為什么API的設計者選擇在這里使用它?
color3f
函數調用color
函數並傳入單個參數 - 數據結構Color3
,數據為rg b。 由於某種原因,此處的數據類型僅為最后一個參數指定(b :: GLfloat)
問題:為什么僅為最后一個參數指定數據類型,為什么在此處指定它?
問題:為什么在調用color
函數color $ Color3 rg (b :: GLfloat)
時使用美元符號?
繼續:
clear
是opengl glClear
包裝器,並將列表作為參數,在這種情況下只包含一個元素[ColorBuffer]
。 renderPrimitive
似乎是opengl glBegin
包裝函數, Quads
似乎是數據類型。 問題:接下來會發生什么? $ do
表示什么? (計算系列? 某事, IO monads?)。 flush
是glFlush
包裝器。 1。 DisplayCallback
是IO ()
的類型同義詞。 您可以使用hoogle查找內容,或在ghci中鍵入:i DisplayCallback
。
IO ()
被賦予特殊名稱的原因是因為GLUT
基於回調:注冊函數以處理特定事件,例如顯示,輸入事件,調整大小事件等。有關完整列表,請參閱文檔 。 顯然,您不需要類型同義詞,但它們是為了清晰和更好的通信而提供的。
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
。 感嘆號表明這些字段很嚴格。 。 為什么這有必要? 好吧,他們可以很容易地使用(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) gb
或者為函數vertex3f
提供類型簽名。
5。 $
只是具有低優先級的函數應用程序,它被定義為f $ a = fa; infixr $ 0
f $ a = fa; infixr $ 0
。 你也可以寫color (Color3 rg (b :: GLfloat))
所以這里純粹是一種風格問題。
6。 也許文檔將再次最好地解釋renderPrimitive
正在做什么。 但是短版本是這樣的:不是在glBegin
- glEnd
塊中寫入內容,而是在renderPrimitive
寫入它。 您不需要編寫glEnd
因為它隱藏在renderPrimitive
。 所以你的代碼相當於:
glBegin (GL_QUADS);
// All the stuff inside goes here ...
glEnd ();
OpenGL
做了一些魔術,以便將c中的異常正確地引入haskell Universe,但你真的不必擔心這一點。
最后評論:如果您計划將haskell用於圖形,那么編寫實際的openGL代碼可能不是最好的主意。 畢竟,如果你打算使用openGL,為什么不用c? 那里有無數的圖形庫。 您應該瀏覽hackage以獲得適合您的包裝。 我擔心我不能推薦任何東西,因為我不熟悉哪些包可用。
我不知道有問題的實際圖書館,但我想我可以回答你的一些觀點。
我希望DisplayCallback
只是一些更復雜的類型簽名的別名。 如果你打開GHCi並導入必要的模塊,你應該可以說
:i DisplayCallback
它會告訴你它的含義。 那么至少你會看到這個定義,即使這並不一定能告訴你它的用途 。
該do
記號不只是為IO單子,這對任何單子 。 如果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
如果你有很多功能,那么獲得正確數量的右括號會很煩人。 對於一個函數調用,這是一個品味問題。
renderPrimitive
函數renderPrimitive
兩個參數。 其中一個是Quads
(無論是什么),另一個是整個do-block 。 因此,您將所有代碼作為參數傳遞給renderPrimitive
函數(可能以某種方式執行它)。
希望這會給你一些啟示。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.