What is the correct way of initializing an elm application

后端 未结 4 1009
有刺的猬
有刺的猬 2020-12-31 02:46

The documentation for Elm\'s Random module states:

A good way to get an unexpected seed is to use the current time. http://package.elm-

相关标签:
4条回答
  • 2020-12-31 03:18

    I reworked the third example from @Apanatshka above, trying to get to simpler code that feels more like the standard architecture, at least as seen in Mike Clark's training videos, and runs under Elm 0.16. Here is the refactored version I came up with:

    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)
    

    This needs special help in the HTML file, so here's a file containing two examples:

    <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>
    
    0 讨论(0)
  • 2020-12-31 03:35

    Just an update for people who got here from Google like I did: the recommended way to do this now is with flags instead of ports. The code in the other answers will not even compile now.

    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 讨论(0)
  • 2020-12-31 03:38

    There are different ways to do this. Each has it's own benefits. I'll give you the three that I know with a similar example for each.

    1) Add a time ticker input

    One thing you could do is add time to the inputs of your program. An example of a tiny program using the current time every second for a random number:

    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
    

    If you need time as an input anyway, and sample your other inputs based on this time, it's the easiest way. (If you already use Time.fps for this, use Time.timestamp to get the actual time with it)

    2) At startup with a signal

    If you don't normally need time as an input to your program, the previous solution is not ideal. You may prefer to initialise your program state with the start time of the program and not have to ignore a time ticker for the rest of the time the program runs.

    It's probably easiest to do this with the signal-extra package*. Use Signal.Time.startTime to get a signal that doesn't tick but only has the start time of the program as the initial value. Use Signal.Extra.foldp' so you can use the initial value of your inputs.

    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
    

    *I may be biased because I'm the author of the linked package. But I don't know of other packages offering the same functionality.

    3) At startup with a port

    If you find the previous solution unsatisfactory, because you have this not-changing Signal to add to your input, this solution is for you. Here we use JavaScript interop to get the program startup time, and Elm will accept it as a constant value (no Signal). The Elm code looks like so:

    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
    

    So what's the downside here? You need to write some JavaScript. Instead of the standard

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

    , you need something like this in your html file:

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

    If you choose this way, perhaps it's a good idea to use a random number from JavaScript as your initial seed. I've read that that's more cryptographically secure (disclaimer: I don't know much about crypto). So you'd have a port aRandomNumber : Int and {aRandomNumber: Math.floor((Math.random() - 0.5) * 4294967295)}.

    0 讨论(0)
  • 2020-12-31 03:41

    If you are using the StartApp then you'll need to use a custom HTML file with

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

    Then to use the startTime as a seed:

    startTimeSeed : Seed
    startTimeSeed = Random.initialSeed <| round startTime
    
    app =
      StartApp.start
        { init = (init startTimeSeed, Effects.none)
        , update = update
        , view = view
        , inputs = []
        }
    

    And then in the code you'll be doing something like

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

    where, for example:

    intList : Random.Generator (List Int)
    intList =
        Random.list 5 (Random.int 0 100)
    
    0 讨论(0)
提交回复
热议问题