Я - студент, в настоящее время узнающий о Функциональной Реактивной парадигме с помощью F#. Это - радикально новая точка зрения для меня. Вчера я узнал о создании простой игры пинг-понга с помощью этой парадигмы. Идея, которую я схватываю до сих пор: мы думаем значения как функции времени. На его чистой форме это является не сохраняющим состояние. Однако я должен помнить положение шара (или состояние). Таким образом, я всегда передаю текущую позицию шара как параметр глобальной функции.
Если мы говорим о небольших более сложных играх, как Space Invaders, у нас есть много состояний (положение посторонних объектов, текущий HP посторонних объектов, количество остающихся бомб, и т.д.)
Существует ли изящный / лучший способ заняться этой проблемой? Мы всегда храним состояния на верхнем уровне? Все текущие состояния должны быть даны как дополнительный входной параметр глобальной функции?
Кто-либо может объяснить эту простую выборку использования на F#?Большое спасибо.
Существует несколько способов выполнения FRP, и это активная область исследований. Что лучше всего, может во многом зависеть от деталей того, как вещи взаимодействуют друг с другом, и в будущем могут появиться новые и лучшие методы.
В целом идея состоит в том, чтобы иметь поведение, которое является функцией времени, а не обычными ценностями (как вы сказали). Поведение может быть определено в терминах другого поведения, и может быть определено для переключения между другим поведением при возникновении определенных событий.
В вашем примере вам обычно не нужно запоминать положение мяча с помощью аргументов (но для некоторых типов FRP вы можете это сделать).Вместо этого вы можете просто иметь поведение:
ballPos: time -> (float * float)
Это может иметь глобальную область видимости, или для более крупной программы может быть лучше иметь локальную область со всеми ее использованием в этот объем.
По мере того, как все становится более сложным, вы будете определять поведение все более сложным образом, зависящее от другого поведения и событий, включая рекурсивные зависимости, которые по-разному обрабатываются в разных средах FRP. Я ожидал, что в F # для рекурсивных зависимостей вам понадобится let rec
, включающая все задействованные поведения. Тем не менее, они все еще могут быть организованы в структуры - на верхнем уровне у вас могут быть:
type alienInfo = { pos : float*float; hp : float }
type playerInfo = { pos : float*float; bombs : int }
let rec aliens : time -> alienInfo array = // You might want laziness here.
let behaviours = [| for n in 1..numAliens ->
(alienPos player n, alienHP player n) |]
fun t -> [| for (posBeh, hpBeh) in behaviours ->
{pos=posBeh t; hp=hpBeh t} |] // You might want laziness here.
and player : time -> playerInfo = fun t ->
{ pos=playerPos aliens t; bombs=playerBombs aliens t}
И затем можно определить поведение для alienPos, alienHP с зависимостями от игрока, а playerPos, playerBombs можно определить с зависимостями от пришельцев .
В любом случае, если вы можете подробнее рассказать о том, какой тип FRP вы используете, вам будет легче дать более конкретный совет. (И если вам нужен совет о том, какой - лично я рекомендую прочитать: http://conal.net/papers/push-pull-frp/push-pull-frp.pdf )
У меня нет опыта реактивного программирования на F #, но проблема глобального состояния в чисто функциональных системах довольно распространена и имеет довольно элегантное решение: Монады .
Хотя сами монады в основном используются в Haskell, основная концепция воплотилась в F # как вычислительные выражения .
Идея состоит в том, что вы на самом деле не меняете состояния, а просто описываете переходы состояния, то есть как создавать новые состояния. Само состояние можно полностью скрыть в программе. Используя специальный монадический синтаксис, вы можете почти обязательно писать чистые, но с сохранением состояния программы.
Взяв (модифицированную) реализацию из этого источника , монада State
могла бы выглядеть так
let (>>=) x f =
(fun s0 ->
let a,s = x s0
f a s)
let returnS a = (fun s -> a, s)
type StateBuilder() =
member m.Delay(f) = f()
member m.Bind(x, f) = x >>= f
member m.Return a = returnS a
member m.ReturnFrom(f) = f
let state = new StateBuilder()
let getState = (fun s -> s, s)
let setState s = (fun _ -> (),s)
let runState m s = m s |> fst
Итак, давайте рассмотрим пример: мы хотим написать функцию, которая может писать значения в журнал (просто список) при продолжении. Поэтому мы определяем
let writeLog x = state {
let! oldLog = getState // Notice the ! for monadic computations (i.e. where the state is involved)
do! setState (oldLog @ [x]) // Set new state
return () // Just return (), we only update the state
}
в состоянии
, теперь мы можем использовать это в императивном синтаксисе без необходимости вручную обрабатывать список журналов.
let test = state {
let k = 42
do! writeLog k // It's just that - no log list we had to handle explicitly
let b = 2 * k
do! writeLog b
return "Blub"
}
let (result, finalState) = test [] // Run the stateful computation (starting with an empty list)
printfn "Result: %A\nState: %A" result finalState
Впрочем, здесь все чисто функционально;)
Томас приятный доклад о реактивном программировании на F #. В вашем случае должны применяться многие концепции.