2015-02-19 21 views
18

dokumentacji dla stanów modułu Random wiąz za:Co to jest poprawny sposób inicjowania aplikacja Elm

dobry sposób, aby uzyskać nieoczekiwany nasienie jest użycie aktualny czas. http://package.elm-lang.org/packages/elm-lang/core/1.1.0/Random

Nie widzę jednak dobry przykład, jak wykonać taką logiką inicjalizacji aplikacji FRP. Na jaki sygnał powinienem zareagować? Jak to zrobić z minimum kodu i maksymalnej jasności.

+0

Omawiamy to teraz na liście mailingowej Elm: https://groups.google.com/d/msg/elm-discuss/ilDp3-ekJ98/cA-oiAAzWskJ –

Odpowiedz

27

Można to zrobić na różne sposoby. Każdy ma swoje zalety. Dam ci trzy, które znam, z podobnym przykładem dla każdego.

1) Dodaj wejście giełdowy czasu

Jedno można zrobić, to dodać razem z wejściami programu. Przykładem malutkim programem używając aktualnego czasu co drugi losową liczbę:

import Time 
import Time (Time, second) 
import Text (asText) 
import Mouse 
import Signal 
import Signal (Signal, (<~), (~)) 
import Random 
import Random (Seed) 
import Graphics.Element (Element) 

randomInt : Seed -> Int 
randomInt seed = seed |> (Random.generate <| Random.int 1 10) |> fst 

otherInput : Signal (Int,Int) 
otherInput = Mouse.position 

timeSeed : Signal Seed 
timeSeed = Random.initialSeed << round <~ Time.every second 

inputs : Signal (Seed,(Int,Int)) 
inputs = (,) <~ timeSeed ~ otherInput 

update : (Seed, (Int,Int)) -> (Int,Int) -> (Int,Int) 
update (seed,(x,y)) (x',y') = 
    let num = randomInt seed 
    in (x-x'-num,y'-y+num) -- this update function is nonsense 

main : Signal Element 
main = asText <~ Signal.foldp update (0,0) inputs 

Jeśli potrzebują czasu jako wejście i tak, i spróbować swoich innych wejść w oparciu o ten czas, to najprostszy sposób. (Jeśli używałeś Time.fps do tego użyć Time.timestamp aby uzyskać aktualny czas z nim)

2) przy starcie z sygnałem

Jeśli nie zwykle potrzebują czasu, jako wkład do twojego programu, poprzednie rozwiązanie nie jest idealne. Możesz preferować inicjalizację stanu programu z czasem rozpoczęcia programu i nie musisz ignorować znacznika czasu przez resztę czasu działania programu.

Najprawdopodobniej najłatwiej zrobić to z signal-extra package *. Użyj Signal.Time.startTime, aby uzyskać sygnał, który nie zaznacza, ale ma tylko czas rozpoczęcia programu jako wartość początkową. Użyj wartości Signal.Extra.foldp', aby użyć początkowej wartości swoich danych wejściowych.

import Time 
import Time (Time, second) 
import Text (asText) 
import Mouse 
import Signal 
import Signal (Signal, (<~), (~)) 
import Random 
import Random (Seed) 
import Graphics.Element (Element) 
import Signal.Extra as SignalE 
import Signal.Time as Time 

randomInt : Seed -> (Int,Seed) 
randomInt seed = (Random.generate <| Random.int 1 10) |> fst 

otherInput : Signal (Int,Int) 
otherInput = Mouse.position 

startTimeSeed : Signal Seed 
startTimeSeed = Random.initialSeed << round <~ Time.startTime 

inputs : Signal (Seed,(Int,Int)) 
inputs = (,) <~ startTimeSeed ~ otherInput 

update (x,y) (seed,(x',y')) = 
    let (num,seed') = randomInt seed 
    in (seed',(x-x'-num,y'-y+num)) 

main : Signal Element 
main = asText <~ SignalE.foldp' (snd >> update) identity inputs 

* Mogę być stronniczy, ponieważ jestem autorem połączonego pakietu. Ale nie znam innych pakietów oferujących tę samą funkcjonalność.

3) Przy starcie z portem

Jeśli okaże poprzednie rozwiązanie niezadowalające, ponieważ masz to nie zmienia Signal dodać do wejścia, to rozwiązanie jest dla Ciebie. Tutaj używamy JavaScript interop, aby uzyskać czas uruchomienia programu, a Wiąz zaakceptuje go jako stałą wartość (nr Signal). Kod wiązów wygląda następująco:

import Time 
import Time (Time, second) 
import Text (asText) 
import Mouse 
import Signal (Signal, (<~)) 
import Random 
import Random (Seed) 
import Graphics.Element (Element) 

port startTime : Float 

randomInt : Seed -> (Int,Seed) 
randomInt seed = (Random.generate <| Random.int 1 10) |> fst 

startTimeSeed : Seed 
startTimeSeed = Random.initialSeed <| round startTime 

update (x,y) (seed,(x',y')) = 
    let (num,seed') = randomInt seed 
    in (seed',(x-x'-num,y'-y+num)) 

main : Signal Element 
main = asText <~ Signal.foldp update (startTimeSeed, (0,0)) Mouse.position 

Co tu jest wadą? Musisz napisać trochę kodu JavaScript. Zamiast standardowego

<script>Elm.fullscreen(Elm.<YourModule>)</script> 

, trzeba coś takiego w pliku html:

<script>Elm.fullscreen(Elm.<YourModule>, {startTime: Date.now()})</script> 

Jeśli wybierzesz ten sposób, być może jest to dobry pomysł, aby wykorzystać liczbę losową z JavaScript jako początkowy nasionko. Czytałem, że jest to bardziej bezpieczne kryptograficznie (zrzeczenie się: nie wiem zbyt wiele o krypto). Więc masz port aRandomNumber : Int i {aRandomNumber: Math.floor((Math.random() - 0.5) * 4294967295)}.

1

Jeśli używasz StartApp następnie trzeba użyć niestandardowego pliku HTML z

<script type="text/javascript"> 
    var yourPgm = Elm.fullscreen(Elm.Main, {startTime: Date.now()}); 
</script> 

następnie użyć startTime jako materiału siewnego:

startTimeSeed : Seed 
startTimeSeed = Random.initialSeed <| round startTime 

app = 
    StartApp.start 
    { init = (init startTimeSeed, Effects.none) 
    , update = update 
    , view = view 
    , inputs = [] 
    } 

a następnie w kodzie będziesz robił coś

init : Seed -> List Int 
init seed = fst <| Random.generate intList seed 

gdzie, na przykład:

intList : Random.Generator (List Int) 
intList = 
    Random.list 5 (Random.int 0 100) 
5

Przeprojektowałem trzeci przykład z @Apanatshki powyżej, próbując dostać się do prostszego kodu, który wydaje się bardziej podobny do standardowej architektury, przynajmniej tak, jak widuje się w filmach szkoleniowych Mike'a Clarka, i działa pod Wiązem 0.16. Oto wersja refactored wymyśliłem:

module PortBasedRandom where 

import Mouse 
import Signal exposing (Signal, map) 
import Random exposing (Seed) 
import Graphics.Element exposing (Element, show) 

port primer : Float 


firstSeed : Seed 
firstSeed = 
    Random.initialSeed <| round primer 


type alias Model = 
    { nextSeed : Seed 
    , currentInt : Int 
    } 


initialModel : Model 
initialModel = 
    { nextSeed = firstSeed 
    , currentInt = 0 
    } 


randomInt : Model -> Model 
randomInt model = 
    let 
     (i, s) = Random.generate (Random.int 1 10) model.nextSeed 
    in 
     { model | nextSeed = s, currentInt = i } 


update : (Int, Int) -> Model -> Model 
update (_, _) model = 
    randomInt model 


main : Signal Element 
main = 
    Signal.foldp update initialModel Mouse.position 
    |> map (\m -> show m.currentInt) 

ta wymaga szczególnej pomocy w pliku HTML, więc o to plik zawierający dwa przykłady:

<html> 
    <head> 
    <title></title> 
    <script src="port_based_random.js"></script> 
    </head> 
    <body> 
    <p>Move your mouse to generate new random numbers between 1 and 10 inclusive.</p> 
    <script>Elm.fullscreen(Elm.PortBasedRandom, {primer: Date.now()})</script> 
    <script>Elm.fullscreen(Elm.PortBasedRandom, {primer: Math.floor((Math.random() - 0.5) * 4294967295)})</script> 
    </body> 
</html> 
1

Wystarczy aktualizacji dla ludzi, którzy dostali się tu od Google tak jak ja: zalecanym sposobem na to teraz jest flags zamiast ports. Kod w innych odpowiedziach nie będzie się teraz kompilował.

https://guide.elm-lang.org/interop/javascript.html

HTML

<script> 
    var app = Elm.Main.fullscreen({myRandomValue: Date.now()}); 
</script> 

Elm

type alias Model = { 
    mySeed : String 
} 

type alias Flags = { 
    myRandomValue : String 
} 

init : Flags -> (Model, Cmd Msg) 
init flags = 
    { 
    mySeed = flags.myRandomValue 
    } 

...

main : Program Flags Model Msg 
main = programWithFlags 
    { 
    view = view, 
    init = init, 
    update = update 
    } 
+0

Nadal bardzo niskiej jakości odpowiedź. Powinieneś dodać przykłady bezpośrednio w kodzie. – timiTao

+0

@timiTao Dzięki za informację zwrotną - dodałem przykładowy kod do mojej odpowiedzi. –