【问题标题】:What is the correct way of initializing an elm application初始化榆树应用程序的正确方法是什么
【发布时间】:2015-04-20 18:57:53
【问题描述】:

Elm 的 Random 模块的文档说明:

获得意外种子的一个好方法是使用当前时间。 http://package.elm-lang.org/packages/elm-lang/core/1.1.0/Random

但是,我看不到如何在 FRP 应用程序中执行此类初始化逻辑的好示例。我应该对什么信号做出反应?如何以最少的代码和最大的清晰度做到这一点。

【问题讨论】:

标签: random functional-programming frp elm


【解决方案1】:

有不同的方法可以做到这一点。每个都有它自己的好处。我会给你三个我知道的,每个都有一个类似的例子。

1) 添加时间标记输入

您可以做的一件事是为程序的输入增加时间。一个小程序使用每秒当前时间作为随机数的示例:

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

如果您仍然需要时间作为输入,并根据此时间对其他输入进行采样,这是最简单的方法。 (如果您已经为此使用Time.fps,请使用Time.timestamp 获取实际时间)

2) 启动时有信号

如果您通常不需要时间作为程序的输入,那么之前的解决方案并不理想。您可能更喜欢使用程序的开始时间来初始化程序状态,而不必在程序运行的其余时间忽略时间标记。

使用signal-extra package* 可能最容易做到这一点。使用Signal.Time.startTime 得到一个不滴答但只有程序开始时间作为初始值的信号。使用Signal.Extra.foldp',这样您就可以使用输入的初始值。

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

*我可能有偏见,因为我是链接包的作者。但我不知道其他提供相同功能的软件包。

3) 使用端口启动时

如果您发现以前的解决方案不满意,因为您有这个不变的Signal 要添加到您的输入中,这个解决方案适合您。这里我们使用JavaScript interop 来获取程序启动时间,Elm 会接受它作为一个常量值(没有Signal)。 Elm 代码如下所示:

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

那么这里的缺点是什么?您需要编写一些 JavaScript。而不是标准

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

,您的 html 文件中需要这样的内容:

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

如果您选择这种方式,那么使用 JavaScript 中的随机数作为初始种子可能是个好主意。我读过这在密码学上更安全(免责声明:我对密码学知之甚少)。所以你会有port aRandomNumber : Int{aRandomNumber: Math.floor((Math.random() - 0.5) * 4294967295)}

【讨论】:

    【解决方案2】:

    我重新编写了上面@Apanatshka 的第三个示例,试图获得更简单的代码,感觉更像标准架构,至少如 Mike Clark 的培训视频中所见,并在 Elm 0.16 下运行。这是我想出的重构版本:

    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)
    

    这需要 HTML 文件中的特殊帮助,所以这里有一个包含两个示例的文件:

    <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>
    

    【讨论】:

      【解决方案3】:

      如果您使用 StartApp,则需要使用自定义 HTML 文件

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

      然后使用 startTime 作为种子:

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

      然后在代码中你会做类似的事情

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

      在哪里,例如:

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

      【讨论】:

        【解决方案4】:

        对于像我一样从 Google 来到这里的人来说只是一个更新:现在推荐的方法是使用 flags 而不是 ports。其他答案中的代码现在甚至无法编译。

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

        HTML

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

        榆树

        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
          }
        

        【讨论】:

        • 仍然是非常低质量的答案。您应该直接在代码中添加一些示例。
        • @timiTao 感谢您的反馈 - 我在答案中添加了一些示例代码。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-02-15
        • 1970-01-01
        • 2017-10-03
        • 1970-01-01
        • 1970-01-01
        • 2019-03-23
        • 1970-01-01
        相关资源
        最近更新 更多