random - significa - por que no se debe utilizar una variable antes de asignarle un valor
¿Cuál es la forma correcta de inicializar una aplicación de olmo? (4)
La documentación para el módulo de Elm Random
dice:
Una buena manera de obtener una semilla inesperada es usar la hora actual. http://package.elm-lang.org/packages/elm-lang/core/1.1.0/Random
No veo, sin embargo, un buen ejemplo de cómo realizar dicha lógica de inicialización en la aplicación FRP. ¿A qué señal debo reaccionar? Cómo hacerlo con un mínimo de código y máximo de claridad.
Hay maneras diferentes de hacer esto. Cada uno tiene sus propios beneficios. Te daré los tres que conozco con un ejemplo similar para cada uno.
1) Añadir una entrada de marcador de tiempo
Una cosa que podría hacer es agregar tiempo a las entradas de su programa. Un ejemplo de un pequeño programa que usa la hora actual cada segundo para un número aleatorio:
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
Si necesita tiempo como una entrada de todos modos, y muestre sus otras entradas basadas en este tiempo, es la forma más fácil. (Si ya usa Time.fps
para esto, use Time.timestamp
para obtener la hora real)
2) Al inicio con una señal.
Si normalmente no necesita tiempo como entrada para su programa, la solución anterior no es la ideal. Es posible que prefiera iniciar el estado de su programa con la hora de inicio del programa y no tener que ignorar un indicador de tiempo por el resto del tiempo que se ejecuta el programa.
Probablemente sea más fácil hacer esto con el paquete de señal adicional *. Use Signal.Time.startTime
para obtener una señal que no marque pero que solo tenga la hora de inicio del programa como valor inicial. Use Signal.Extra.foldp''
para que pueda usar el valor inicial de sus entradas.
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
* Puede que esté sesgado porque soy el autor del paquete vinculado. Pero no conozco otros paquetes que ofrezcan la misma funcionalidad.
3) Al inicio con un puerto.
Si encuentra que la solución anterior no es satisfactoria, ya que tiene esta Signal
no cambio para agregar a su entrada, esta solución es para usted. Aquí usamos interoperabilidad de JavaScript para obtener el tiempo de inicio del programa, y Elm lo aceptará como un valor constante (sin Signal
). El código de Elm se ve así:
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
Entonces, ¿cuál es el inconveniente aquí? Necesitas escribir algún JavaScript. En lugar de la norma
<script>Elm.fullscreen(Elm.<YourModule>)</script>
, necesitas algo como esto en tu archivo html:
<script>Elm.fullscreen(Elm.<YourModule>, {startTime: Date.now()})</script>
Si elige de esta manera, tal vez sea una buena idea usar un número aleatorio de JavaScript como inicial inicial. He leído que eso es más seguro criptográficamente (exención de responsabilidad: no sé mucho acerca de criptografía). Entonces tendrías un port aRandomNumber : Int
y {aRandomNumber: Math.floor((Math.random() - 0.5) * 4294967295)}
.
Revisé el tercer ejemplo de @Apanatshka arriba, tratando de llegar a un código más simple que se parezca más a la arquitectura estándar, al menos como se ve en los videos de entrenamiento de Mike Clark, y se ejecuta bajo Elm 0.16. Aquí está la versión refactorizada que se me ocurrió:
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)
Esto necesita ayuda especial en el archivo HTML, así que aquí hay un archivo que contiene dos ejemplos:
<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>
Si está utilizando StartApp, deberá utilizar un archivo HTML personalizado con
<script type="text/javascript">
var yourPgm = Elm.fullscreen(Elm.Main, {startTime: Date.now()});
</script>
Luego, para utilizar el tiempo de inicio como una semilla:
startTimeSeed : Seed
startTimeSeed = Random.initialSeed <| round startTime
app =
StartApp.start
{ init = (init startTimeSeed, Effects.none)
, update = update
, view = view
, inputs = []
}
Y luego en el código harás algo como
init : Seed -> List Int
init seed = fst <| Random.generate intList seed
donde, por ejemplo:
intList : Random.Generator (List Int)
intList =
Random.list 5 (Random.int 0 100)
Solo una actualización para las personas que llegaron aquí desde Google como lo hice yo: la forma recomendada de hacerlo ahora es con flags
lugar de ports
. El código en las otras respuestas ni siquiera se compilará ahora.
https://guide.elm-lang.org/interop/javascript.html
HTML
<script>
var app = Elm.Main.fullscreen({myRandomValue: Date.now()});
</script>
Olmo
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
}