Отказ от ответственности: я не могу комментировать то, что «рекомендуется», но я могу показать способ, который делает то, что вы хотите сделать.
Я хочу описать два метода:
Первый – это использование проводов с отслеживанием состояния, и он очень похож на это немного устаревшее руководство 2013 года, но оно основано на Netwire 5.0.2.
Второй – использование проводов без сохранения состояния. Поскольку они не имеют состояния, им необходимо возвращать свои предыдущие значения, что делает типы ввода проводов и окончательную комбинацию проводов немного более запутанными. В остальном они довольно похожи.
Основные типы, задействованные в обоих примерах:
type Collision = Bool
type Velocity = Float
type Position = Float
с сохранением состояния
Вы можете смоделировать свою проблему с помощью двух проводов (с сохранением состояния), которые затем объединяются.
Один провод моделирует постоянную скорость и меняет направление при столкновении. (Упрощенный) тип этого — Wire s e m Collision Velocity
, т. е. вход — если произошло столкновение, а вывод — текущая скорость.
Другой моделирует положение и обрабатывает коллизии. (Упрощенный) тип этого — Wire s e m Velocity (Position, Collision)
, т. е. он берет скорость, вычисляет новую позицию и возвращает ее, если произошло столкновение.
Наконец, скорость подается на провод положения, а результат столкновения возвращается обратно на провод скорости.
Давайте посмотрим на детали скоростной проволоки:
-- stateful fixed velocity wire that switches direction when collision occurs
velocity :: Velocity -> Wire s e m Collision Velocity
velocity vel = mkPureN $ \collision ->
let nextVel = if collision then negate vel else vel
in (Right nextVel, velocity nextVel)
mkPureN
создает проводник с состоянием, который зависит только от ввода и собственного состояния (не от монады или времени). Состояние - это текущая скорость, а следующая скорость инвертируется, если в качестве входных данных передается Collision=True
. Возвращаемое значение представляет собой пару значения скорости и нового провода с новым состоянием.
Для позиции уже недостаточно использовать провод integral
напрямую. Нам нужна расширенная, «ограниченная» версия integral
, которая гарантирует, что значение остается ниже верхней границы и больше 0, и возвращает информацию, если такое столкновение произошло.
-- bounded integral [0, bound]
pos :: HasTime t s => Position -> Position -> Wire s e m Velocity (Position, Collision)
pos bound x = mkPure $ \ds dx ->
let dt = realToFrac (dtime ds)
nextx' = x + dt*dx -- candidate
(nextx, coll)
| nextx' <= 0 && dx < 0 = (-nextx', True)
| nextx' >= bound && dx > 0 = (bound - (nextx' - bound), True)
| otherwise = (nextx', False)
in (Right (nextx, coll), pos bound nextx)
mkPure
похоже на mkPureN
, но позволяет проводнику зависеть от времени.
dt
— это разница во времени.
nextx'
— это новая позиция, которую возвращает integral
.
Следующие строки проверяют границы и вернуть новую позицию, если произошло столкновение и новый провод с новым состоянием.
Наконец, вы вводите их друг в друга, используя rec
и delay
. Полный пример:
{-# LANGUAGE Arrows #-}
module Main where
import Control.Monad.Fix
import Control.Wire
import FRP.Netwire
type Collision = Bool
type Velocity = Float
type Position = Float
-- bounded integral [0, bound]
pos :: HasTime t s => Position -> Position -> Wire s e m Velocity (Position, Collision)
pos bound x = mkPure $ \ds dx ->
let dt = realToFrac (dtime ds)
nextx' = x + dt*dx -- candidate
(nextx, coll)
| nextx' <= 0 && dx < 0 = (-nextx', True)
| nextx' >= bound && dx > 0 = (bound - (nextx' - bound), True)
| otherwise = (nextx', False)
in (Right (nextx, coll), pos bound nextx)
-- stateful fixed velocity wire that switches direction when collision occurs
velocity :: Velocity -> Wire s e m Collision Velocity
velocity vel = mkPureN $ \collision ->
let nextVel = if collision then negate vel else vel
in (Right nextVel, velocity nextVel)
run :: (HasTime t s, MonadFix m) => Position -> Velocity -> Position -> Wire s () m a Position
run start vel bound = proc _ -> do
rec
v <- velocity vel <<< delay False -< collision
(p, collision) <- pos bound start -< v
returnA -< p
main :: IO ()
main = testWire clockSession_ (run 0 5 20)
без гражданства
Вариант без сохранения состояния очень похож на вариант с сохранением состояния, за исключением того, что состояние переходит к типу ввода проводов, а не является параметром функций, создающих проводник.
Таким образом, провод скорости принимает кортеж (Velocity, Collision)
в качестве входных данных, и мы можем просто поднять функцию, чтобы создать его:
-- pure stateless independent from time
-- output velocity is input velocity potentially negated depending on collision
velocity :: Monad m => Wire s e m (Velocity, Collision) Velocity
velocity = arr $ \(vel, collision) -> if collision then -vel else vel
Вы также можете использовать функцию mkSF_
из Control.Wire.Core
(и тогда избавиться от ограничения на Monad m
).
pos
становится
-- pure stateless but depending on time
-- output position is input position moved by input velocity (depending on timestep)
pos :: HasTime t s => Position -> Wire s e m (Position, Velocity) (Position, Collision)
pos bound = mkPure $ \ds (x,dx) ->
let dt = realToFrac (dtime ds)
nextx' = x + dt*dx -- candidate
(nextx, coll)
| nextx' <= 0 && dx < 0 = (-nextx', True)
| nextx' >= bound && dx > 0 = (bound - (nextx' - bound), True)
| otherwise = (nextx', False)
in (Right (nextx, coll), pos bound)
Здесь нам по-прежнему нужно использовать mkPure, потому что нет функции, которую можно было бы использовать специально для проводов без сохранения состояния, которые зависят от времени.
Чтобы соединить два провода, мы теперь должны передать выход скорости в себя и положение, а из провода pos
положение в себя и информацию о столкновении в провод скорости.
Но на самом деле с проводами без сохранения состояния вы также можете разделить части «интеграции» и «проверки границ» в проводе pos
. Затем провод pos
представляет собой Wire s e m (Position, Velocity) Position
, который напрямую возвращает то, что nextx'
указано выше, а провод boundedPos
представляет собой Wire s e m (Position, Velocity) (Position, Collision)
, который получает новое положение из pos
и скорости, а также применяет проверку границ. Таким образом, различные логические части будут хорошо разделены.
Полный пример:
{-# LANGUAGE Arrows #-}
module Main where
import Control.Monad.Fix
import Control.Wire
import FRP.Netwire
type Collision = Bool
type Velocity = Float
type Position = Float
-- pure stateless but depending on time
-- output position is input position moved by input velocity (depending on timestep)
pos :: HasTime t s => Wire s e m (Position, Velocity) Position
pos = mkPure $ \ds (x,dx) ->
let dt = realToFrac (dtime ds)
in (Right (x + dt*dx), pos)
-- pure stateless independent from time
-- input position is bounced off the bounds
boundedPos :: Monad m => Position -> Wire s e m (Position, Velocity) (Position, Collision)
boundedPos bound = arr $ \(x, dx) ->
let (nextx, collision)
| x <= 0 && dx < 0 = (-x, True)
| x >= bound && dx > 0 = (bound - (x - bound), True)
| otherwise = (x, False)
in (nextx, collision)
-- pure stateless independent from time
-- output velocity is input velocity potentially negated depending on collision
velocity :: Monad m => Wire s e m (Velocity, Collision) Velocity
velocity = arr $ \(vel, collision) -> if collision then -vel else vel
-- plug the wires into each other
run :: (HasTime t s, MonadFix m) => Position -> Velocity -> Position -> Wire s () m a Position
run start vel bound = proc _ -> do
rec
v <- velocity <<< delay (vel, False) -< (v, collision)
lastPos <- delay start -< p'
p <- pos -< (lastPos, v)
(p', collision) <- boundedPos bound -< (p, v)
returnA -< p'
main :: IO ()
main = testWire clockSession_ (run 0 5 20)
person
E4z9
schedule
25.11.2016