2015-02-18 5 views
5

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

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

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. Я думаю (если мое понимание правильное), что этот параметр предназначен для отправки событий на провод, и что, поскольку мои провода не используют какие-либо события, это «безопасно», но он чувствует себя плохо (EDIT возможно «события «здесь неправильное слово - учебник описывает его как« блокировку значений », но точка все еще стоит - я никогда не собираюсь блокировать и не использовать параметр e в любом месте моего провода). Я посмотрел, была ли версия stepWire для ситуации, когда вы знаете, что у вас никогда не было события, и вы бы не ответили на него, даже если у вас его есть, но он не смог его увидеть. Я попытался сделать проводы e параметром (), а затем проехать Right() всюду, что чувствует себя менее грязным, чем undefined, но все еще не совсем кажется моим намерением.

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

Так что, хотя у меня есть код, который работает, я остаюсь с беспокойным чувством, что я «делаю это неправильно», и так как Netwire 5 довольно новичок, трудно найти примеры «идиоматического» кода, который я может проверить и посмотреть, рядом ли я с маркой. Является ли это тем, как библиотека предназначена для использования или я что-то упускаю?

EDIT: Мне удалось решить вторую проблему я упоминаю (сопоставление с образцом на 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 

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

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

+0

Я стараюсь не использовать эффекты в своих проводах.События передаются в «основной провод», а монада остается полиморфной. – phaazon

ответ

1

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

Это означает, что вы игнорируете важный пример Alternative для входных проводов. Она предоставляет оператору <|>, который работает в очень хороший способ:

Предположим, что мы имеем два провода:

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, вы будете вращаться в два раза быстрее!

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

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

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.

+0

Я думаю, что мои ответы http://stackoverflow.com/questions/32745934/kleisli-arrow-in-netwire-5 и http://stackoverflow.com/questions/30992299/console-interactivity-in-netwire также помогают –

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