2014-02-21 2 views
2

Я учусь Haskell, прыгнув прямо в OpenGL, и я не могу расшифровать этот код:Как работает этот код 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 

Мое понимание до сих пор:

  1. display является функцией. ВОПРОС: Что такое DisplayCallback?
  2. do указывает цепочки вычислений, что-то делать с IO монад
  3. color3f и vertex3f локальные функции с тремя аргументами, определенные в do используя let ключевое слово
  4. я предполагаю, что color и vertex является функцией OPENGL обертка для glColor* и glVertex*.

Теперь это, где это становится запутанным:

  • Color3 и Vertex3, кажется, какая-то структура данных, состоящая из трех аргументов. ВОПРОС: Зачем нужна структура данных здесь? Почему дизайнеры API решили использовать его здесь?

  • color3f Функциональные вызовы color Функция и передает в одном аргументе - структура данных Color3 с данными r g b. По какой-то причине тип данных здесь указан только для последнего аргумента (b :: GLfloat)ВОПРОС: Почему тип данных указан только для последнего аргумента, почему он указан здесь вообще?

  • ВОПРОС: Почему знак доллара используется при вызове функции colorcolor $ Color3 r g (b :: GLfloat)?

Продолжая:

  1. clear является оболочкой для OpenGL glClear и принимает в качестве аргумента список, в данном случае, содержащий только один элемент [ColorBuffer].
  2. renderPrimitive, как представляется, является оберточной функцией для opengl glBegin, Quads, по-видимому, является типом данных. ВОПРОС: Что будет дальше? Что указывает $ do? (серия вычислений? что-то, что-то IO monads?).
  3. flush является оберткой для glFlush.
+3

(Обучение с использованием привязки к большой внешней библиотеке может исказить вашу картину Haskell, так как ваш опыт будет искажен решениями, принятыми (а) решениями, включая выбор языка, авторов OpenGL и (б) решения, в том числе обязательные для функционального перевода, авторов Haskell-связывания. В частности, эти огромные блоки монадического запаха кода императива, и я бы пошел на некоторые длины при написании кода Haskell, чтобы этого избежать. К сожалению, некоторые внешние библиотеки как правило, толкают вас в этом унииоматическом направлении.) – AndrewC

+0

Я прыгнул прямо с OpenGL (с которым я знаком), потому что компьютерная графика и gamedev - это именно то, что я собираюсь использовать для Haskell. По возможности я постараюсь придерживаться идиоматического Haskell и чистого функционального программирования. – JBeurer

ответ

6

1.DisplayCallback - это синоним типа IO(). Вы можете использовать hoogle для поиска вещей, или введите :i DisplayCallback в ghci.

Причина, по которой IO() дается специальное название потому, что GLUT основывается на обратных вызовов: функции регистрируются для обработки определенного события, например, отображение, события ввода, изменение размеров события и т.д. Для полного списка, см docs , Очевидно, вам не нужны синонимы типов, но они предоставляются для ясности и лучшего общения.

2. «Я предполагаю, что цвет и вершина - это функция обложки openGL для glColor * и glVertex *». Не совсем - OpenGL - это обертка вокруг более простых OpenGLRaw, которая представляет собой отображение библиотеки op opl для 1: 1 в haskell. 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(), но тогда функция, принимающая цвет, неотличима от одного, взявшего вектор, которые являются «идеологически» разными объектами, даже если они представлены точно такими же данными (Vertex is data Vertex3 a = Vertex3 !a !a !a). Поэтому вы не хотите передавать вершину функции, требующей цвета. Кроме того, эти типы данных дают лучшую производительность, в идеале, из-за строгости.

4. Почему этот тип указан вообще? Короткий ответ - это требует система типа. Тип литерала - Num a => a, который слишком полиморфен для типа данных, для которого требуется конкретный тип. Таким образом, вы выбираете конкретный тип, используя аннотацию типа.

Почему это требуется только в одном месте? Компилятор может вывести тип других полей. Оглянитесь на объявление типа данных - все три поля должны иметь один и тот же тип. Если одно поле аннотируется, остальные тривиально выводятся. Вы также можете написать 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(); 

OpenGL делает некоторую магию, чтобы принести исключения из с в Haskell вселенной правильно, но вы действительно не нужно беспокоиться об этом.

Заключительный комментарий: Если вы планируете использовать haskell для графики, написание фактического кода openGL, вероятно, не самая лучшая идея. В конце концов, если вы собираетесь использовать openGL, почему бы просто не использовать c? Там есть огромное количество графических библиотек. Вы должны просмотреть hackage для пакета, который подходит именно вам.Боюсь, я не могу ничего рекомендовать, поскольку я не знаком с тем, какие пакеты доступны.

+2

Спасибо. Фактический код OpenGL обычно заканчивается лишь умной частью общего решения. Haskell кажется кратким и выразительным, и именно это я и ищу. Кажется, стоит изучать и изучать. – JBeurer

+0

Что касается 5., почему я не могу указать ограничения типа, написав: color3f (r :: GLfloat) g b = цвет $ Color3 r g b? – JBeurer

+0

Я также попытался указать ограничения типа, используя color3f :: (GLfloat a) => a -> a -> a -> IO(), который тоже не работает. – JBeurer

2

Я не знаю фактическую библиотеку, о которой идет речь, но я думаю, что могу ответить на несколько ваших вопросов.

Я бы ожидал, что DisplayCallback является просто псевдонимом для более сложной сигнатуры типа.Если вы откроете GHCI и импортировать необходимые модули, вы должны быть в состоянии сказать

:i DisplayCallback 

и скажу вам, что это относится. Тогда, по крайней мере, вы увидите определение, даже если это не обязательно скажет вам, что это такое за.

Обозначение do не только для монады IO, но и для любая монада. Я не удивлюсь, если операции рисования OpenGL происходят в их собственной специализированной монаде.

Почему именно 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 функция принимает аргументы. Один из них - Quads (независимо от того, что есть), а другой - весь блок do. Таким образом, вы передаете весь этот код в качестве аргумента функции renderPrimitive (которая, по-видимому, каким-то образом ее выполняет).

Надеюсь, это даст вам некоторое просветление.

+0

Я просвещен, спасибо. :) – JBeurer

Смежные вопросы