初始化榆树应用程序的正确方法是什么

What is the correct way of initializing an elm application

Elm 的 Random 模块的文档指出:

A good way to get an unexpected seed is to use the current time. http://package.elm-lang.org/packages/elm-lang/core/1.1.0/Random

但是我没有看到一个很好的例子来说明如何在 FRP 应用程序中执行这种初始化逻辑。我应该对什么信号做出反应?如何以最少的代码和最大的清晰度来做到这一点。

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

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 for this, use Time.timestamp 来获取实际时间)

2) 启动时有信号

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

使用 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' 执行此操作可能最简单,因此您可以使用输入的初始值。

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

如果您使用的是 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)

我修改了上面@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>

像我一样从 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
  }