Elm的Random
模块的文档说明:
获得意外种子的好方法是使用当前时间。 http://package.elm-lang.org/packages/elm-lang/core/1.1.0/Random
然而,我没有看到如何在FRP应用程序中执行此类初始化逻辑的良好示例。我应该对哪个信号做出反应?如何使用最少的代码和最大的清晰度来做到这一点。
答案 0 :(得分:27)
有不同的方法可以做到这一点。每个人都有自己的好处。我会给你三个我知道的,每个都有类似的例子。
您可以做的一件事是为您的程序输入添加时间。一个小程序的例子,每秒使用一个随机数的当前时间:
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
获取实际时间)
如果您通常不需要时间作为程序的输入,则以前的解决方案并不理想。您可能更喜欢使用程序的开始时间初始化程序状态,而不必在程序运行的其余时间忽略时间标记。
使用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
*我可能有偏见,因为我是链接包的作者。但我不知道其他提供相同功能的软件包。
如果您发现以前的解决方案不能令人满意,因为您要将此不变的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)}
。
答案 1 :(得分:5)
我从上面的@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>
答案 2 :(得分:1)
如果您使用的是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)
答案 3 :(得分:1)
对于像我一样从谷歌来到这里的人的更新:现在推荐的方法是使用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
}