免責事項:「推奨」とは何かについてコメントすることはできませんが、あなたがやりたいことを行う方法を示すことはできます.
2 つの方法について説明します。1 つ
目はステートフル ワイヤを使用する方法で、2013 年の少し古いチュートリアルですが、Netwire 5.0.2 に基づいています。
2 つ目は、ステートレス ワイヤを使用することです。それらはステートレスであるため、以前の値をフィードバックする必要があります。これにより、ワイヤーの入力タイプとワイヤーの最終的な組み合わせが少し複雑になります。それ以外はかなり似ています。
両方の例に含まれる基本型は次のとおりです。
type Collision = Bool
type Velocity = Float
type Position = Float
ステートフル
2 つの (ステートフルな) ワイヤを組み合わせて問題をモデル化できます。
1 本のワイヤは、一定の速度をモデル化し、衝突が発生すると方向を変えます。これの (簡略化された) タイプは ですWire s e m Collision Velocity
。つまり、入力は衝突が発生した場合であり、出力は現在の速度です。
もう 1 つは位置をモデル化し、衝突を処理します。これの (簡略化された) タイプは です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
最後に、 andを使用してそれらを相互にフィードします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_
from を使用することもできますControl.Wire.Core
(その後、 to の制限を取り除き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 を使用する必要があります。これは、 time に依存するステートレス ワイヤに特に使用できる関数がないためです。
2 本のワイヤを接続するには、速度の出力をワイヤ自体と位置に送り、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)