Правильное использование Netwire (5)

Я давно хотел попробовать FRP, и вчера я наконец-то укусил пулю и начал, используя Netwire 5 для начала (довольно произвольный выбор сам по себе, но мне нужно с чего-то начинать!). Мне удалось дойти до точки "кода, который работает", но я заметил несколько шаблонов, которые, я не уверен, являются частью того, как ожидается использование библиотеки, или являются ли они признаком того, что я " Я делаю что-то не так.

Я начал с этого урока, которого было достаточно, чтобы довольно легко настроить и запустить меня - теперь у меня есть вращающийся куб, управляемый простым проводом "возрастающего числа":

spin :: (HasTime t s, Monad m) => Wire s e m a GL.GLfloat
spin = integral 0 . 5

и приложение будет закрываться при нажатии "Esc", используя провода, поставляемые в netwire-input-glfw:

shouldQuit :: (Monoid e, Functor m, Monad m) => Wire s e (GLFWInputT m) a a
shouldQuit = keyPressed GLFW.Key'Escape

Важное различие между ними заключается в том, что spin никогда не запрещает - он всегда должен возвращать какое-то значение shouldQuit тормозит все время; пока на самом деле не будет нажата клавиша, в этом случае я выйду из приложения.

Меня беспокоит то, как мне приходится использовать эти провода. Прямо сейчас это выглядит примерно так:

(wt', spinWire') <- stepWire spinWire st $ Right undefined
((qt', quitWire'), inp'') <- runStateT (stepWire quitWire st $ Right undefined) inp'

case (qt', wt') of
  (Right _, _) -> return ()
  (_, Left _)  -> return () -- ???
  (_, Right x) -> --do things, render, recurse into next frame

Есть две вещи в этом шаблоне, которые заставляют меня чувствовать себя неловко. Во-первых, тот факт, что я прохожу Right undefined на оба звонка stepWire, Я думаю (если я правильно понимаю), что этот параметр предназначен для отправки событий на провод, и что, поскольку мои проводники не используют события, это "безопасно", но это плохо (ПРАВКА, может быть, "события" неправильное слово здесь - в учебнике это описывается как "блокировка значений", но точка зрения остается неизменной - я никогда не собираюсь блокировать и не использую e параметр где угодно в моем проводе). Я посмотрел, чтобы увидеть, была ли версия stepWire для ситуации, когда вы знаете, что у вас никогда нет события, и вы бы не отреагировали на него, даже если бы у вас было событие, но вы его не видели. Я пытался сделать провода e параметр () а затем прохождение Right () везде, который чувствует себя немного менее грязным, чем undefined, но все еще не совсем отражает мое намерение.

Точно так же возвращаемое значение также является Either, Это идеально подходит для shouldQuit провод, но обратите внимание, я должен соответствовать шаблону на wt', выход из spin провод. Я действительно не знаю, что это будет значить для этого, поэтому я просто return (), но я могу представить, что это становится громоздким, так как количество проводов увеличивается, и опять же, это просто не похоже на то, что представляет мое намерение - иметь провод, который никогда не запрещает и на который я всегда могу положиться, чтобы удерживать следующее значение,

Поэтому, хотя у меня есть код, который работает, у меня остается неприятное ощущение, что я как-то "делаю это неправильно", и, поскольку Netwire 5 является довольно новым, трудно найти примеры "идиоматического" кода, который я могу проверить и посмотреть, если я рядом с отметкой. Это как библиотека предназначена для использования или я что-то упустил?

РЕДАКТИРОВАТЬ: мне удалось решить вторую проблему, которую я упоминаю (сопоставление с образцом на Either Результат spin) путем объединения spin а также shouldQuit в один Wire:

shouldContinuePlaying :: (Monoid e, Functor m, Monad m) => Wire s e (GLFWInputT m) a a
shouldContinuePlaying = keyNotPressed GLFW.Key'Escape

game :: (HasTime t s, Monoid e, Functor m, Monad m) => Wire s e (GLFWInputT m) a GL.GLfloat
game = spin . shouldContinuePlaying

Переход по этому проводу дает мне гораздо более разумное возвращаемое значение - если это Left Я могу выйти, в противном случае у меня есть часть данных для работы. Это также намекает на большую степень сочетаемости, чем мой оригинальный метод.

Я все еще должен пройти Right undefined как вход для этого нового провода, хотя. Правда, сейчас есть только один из них, но я все еще не уверен, что это правильный подход.

1 ответ

Решение

На самом верхнем уровне вашей программы у вас будет провод, который имеет (сокращенно) тип Wire a b, Это должно быть передано что-то типа a и он вернет что-то типа b каждый раз, когда вы делаете шаг. Например, оба a а также b может быть какой-то WorldState для игры или, может быть, [RigidBody] для симулятора физики. На мой взгляд, это нормально, чтобы пройти Right undefined на верхнем уровне.

При этом вы игнорируете важное Alternative экземпляр Wire a b для входных проводов. Предоставляет оператор <|> это работает очень хорошо:

Предположим, у нас есть два провода:

w1 :: Wire a b
w2 :: Wire a b

Если w1 тормозит, то

w1 <|> w2 == w2

Если w1 не запрещает, то

w1 <|> w2 == w1

Это означает, что w1 <|> w2 будет тормозить только если оба w1 а также w2 тормозят. Это здорово, это означает, что мы можем делать такие вещи, как:

spin :: (HasTime t s, Monad m) => Wire s e m a GL.GLfloat
spin = integral 0 . (10 . keyPressed GLFW.Key'F <|> 5)

Когда вы нажимаете F, вы будете вращаться в два раза быстрее!

Если вы хотите изменить семантику провода после нажатия кнопки, вам нужно быть немного более креативным, но не намного. Если ваш провод ведет себя по-разному, это означает, что вы делаете какое-то переключение. Документация для выключателей в основном требует от вас следовать типам.

Вот провод, который будет действовать как провод идентификации, пока вы не нажмете данную клавишу, а затем заблокирует навсегда:

trigger :: GLFW.Key -> GameWire a a
trigger key =
  rSwitch mkId . (mkId &&& ((now . pure mkEmpty . keyPressed key) <|> never))

С этим вы можете делать классные вещи, как:

spin :: (HasTime t s, Monad m) => Wire s e m a GL.GLfloat
spin = integral 0 . spinSpeed
  where
    spinSpeed = 5 . trigger GLFW.Key'F --> 
                -5 . trigger GLFW.Key'F -->
                spinSpeed

Это будет переключать счетчик между движением вперед и назад, когда вы нажмете F,

Другие вопросы по тегам