2010-07-28 16 views
22

Jestem studentem uczącym się obecnie o paradygmacie Reaktywnego Reaktywnego za pomocą F #. To dla mnie radykalnie nowy punkt widzenia. Wczoraj dowiedziałem się o stworzeniu prostej gry w ping-ponga przy użyciu tego paradygmatu. Idea, którą uchwycę do tej pory, to: uważamy wartości za funkcje czasu. W czystej postaci jest bezpaństwowcem. Jednak muszę zapamiętać pozycję piłki (lub stan). Dlatego zawsze przekazuję aktualną pozycję piłki jako parametr funkcji globalnej.Funkcjonalne reaktywne F # - Przechowywanie stanów w grach

Jeśli mówimy o niewielkich bardziej skomplikowanych gier, takie jak Space Invaders, mamy wiele państw (pozycja, kosmici obcych prąd KM, liczba pozostałych bomb, itp)

Czy istnieje elegancki/najlepszy sposób rozwiązania tego problemu? Czy zawsze przechowujemy stany na najwyższym poziomie? Czy wszystkie aktualne stany powinny być podane jako dodatkowy argument wejściowy funkcji globalnej?

Czy ktoś może to wyjaśnić za pomocą prostej próbki na F #? Wielkie dzięki.

Odpowiedz

13

Istnieje więcej niż jeden sposób, aby zrobić FRP i jest to aktywny obszar badań. To, co najlepsze, może w dużej mierze zależeć od tego, jak rzeczy wchodzą ze sobą w interakcje, a nowe i lepsze techniki mogą pojawić się w przyszłości.

Ogólnie rzecz biorąc, chodzi o to, aby zachowania, które są funkcjami czasu zamiast zwykłych wartości (jak powiedziałeś). Zachowania można definiować w kategoriach innych zachowań i można je definiować w celu zamiany innych zachowań w przypadku wystąpienia określonych zdarzeń.

W twoim przykładzie, na ogół nie musisz pamiętać pozycji piłki za pomocą argumentów (ale dla niektórych rodzajów FRP możesz zrobić).Zamiast tego możesz po prostu mieć zachowanie:
ballPos : time -> (float * float)
Może to mieć zasięg globalny lub w przypadku większego programu może być lepiej mieć zakres lokalny z wszystkimi jego zastosowaniami w tym zakresie.

W miarę, jak rzeczy stają się bardziej skomplikowane, będziesz miał zdefiniowane zachowania w coraz bardziej złożony sposób, zależą od innych zachowań i zdarzeń - w tym rekursywne zależności, które są obsługiwane w różny sposób w różnych ramach FRP. W F # dla zależności rekurencyjnych oczekiwałbym, że będziesz potrzebował let rec, w tym wszystkich zaangażowanych zachowań. Mogą one być nadal zorganizowane w struktury choć - na górnym poziomie mogą mieć:

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} 

a następnie zachowań dla alienPos, alienHP można zdefiniować, z zależnościami na odtwarzaczu i playerPos, playerBombs można zdefiniować z zależnościami od kosmitów.

W każdym razie, jeśli możesz podać więcej szczegółów na temat rodzaju używanego FRP, łatwiej będzie udzielić bardziej szczegółowych porad. (A jeśli potrzebujesz porady, jakiego rodzaju - osobiście polecam lekturę: http://conal.net/papers/push-pull-frp/push-pull-frp.pdf)

6

Nie mam żadnego doświadczenia z programowaniem reaktywnym pod F #, ale problem globalnego stanu w czysto funkcjonalnych systemach jest dość powszechny i ​​ma dość eleganckie rozwiązanie: Monady.

Podczas gdy monady są używane przede wszystkim w Haskell, koncepcja leżała u podstaw F # jako computation expressions.

Chodzi o to, że tak naprawdę nie zmienia się stanów, ale po prostu opisuje przejścia stanu, tj. Jak wytwarzać nowe stany. Sam stan może być całkowicie ukryty w programie. Używając specjalnej monadycznej składni, możesz pisać czysto, ale programowo, niemal niezauważalnie.

Biorąc (zmodyfikowany) wdrożenie z this source The State monada może wyglądać następująco

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 

Warto więc mieć przykład: Chcemy napisać funkcję, która może zapisywać wartości w dzienniku (tylko lista) podczas dalszego postępowania. Dlatego określenie

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 
} 

ciągu state możemy teraz wykorzystać to w składni bezwzględnej bez konieczności ręcznego obsługiwać listy dziennika.

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 

Mimo wszystko jest czysto funkcjonalny tutaj;)

+0

Dotyczy to głównie monady państwowej. Funkcjonalne programowanie bierne często obejmuje monady, ale zazwyczaj nie jest to prosta monada państwowa. – RD1

+0

Jak już powiedziałem, nie mam doświadczenia w FRP. Niemniej jednak monada państwowa (lub monady) wydaje się być koncepcją, o którą poproszono - wygodnie przechowując i modyfikując dane kontekstowe bez utraty przejrzystości referencyjnej. Jeśli FTP już korzysta z monadycznej infrastruktury, tym lepiej. Transformator stanu monady powinien to zrobić (czy masz na myśli to z * prostym rodzajem *?). Ale bez wyjaśnienia podstaw, informacje te byłyby bezużyteczne! – Dario

+0

Głównym punktem FRP jest umożliwienie definiowania zachowań jako ciągłych funkcji czasu - np. można określić pozycję z kulki pod grawitacją jako z (t) = 9,8 * t * t. Stan monadyczny ma znaczenie tylko dla stanu, który wprowadza dyskretne zmiany - dozwolone są również dyskretne zmiany w FRP, ale są one mniej centralne i często nie pasują do dokładnej formy monady. – RD1

3

Tomas dał nice talk o biernej programowania w F #. W twoim przypadku powinno się stosować wiele koncepcji.

+1

Funkcjonalne programowanie reaktywne to coś więcej niż tylko programowanie reaktywne w języku funkcjonalnym. Główną techniką jest reprezentowanie zachowań jako funkcji czasu. Te zachowania mogą zależeć od siebie nawzajem i od wydarzeń. Tak więc ta rozmowa nie ma tak bezpośredniego znaczenia - ma pewne znaczenie, ale tylko dlatego, że wydarzenia są częścią FRP. (Zgadzam się, że to dobra rozmowa). – RD1

0

Może będziesz chciał spojrzeć na FsReactive.

+6

Wyjaśnienie, w jaki sposób FsReactive pomaga odpowiedzieć na pytanie, zwiększy przydatność Twojej odpowiedzi. –

0

Elm to nowoczesna implementacja FRP. Do modelowania dynamicznych kolekcji, które są wszechobecne w grach takich jak Space Invaders, zawiera on Automaton library oparty na koncepcjach strzałkowego FRP. Powinieneś zdecydowanie to sprawdzić.