functional programming - started - ¿Cómo obtengo la hora actual en Elm 0.17/0.18?
instalar elm (6)
Una opción sin tener que buscar el tiempo en cada ruta de actualización sería envolver su Msg
en otro tipo de mensaje que recuperaría la hora y luego llamar a su update
normal con el tiempo. Esta es una versión modificada de http://elm-lang.org/examples/buttons que actualizará una marca de tiempo en el modelo con cada actualización.
import Html exposing (div, button, text)
import Html.App exposing (program)
import Html.Events exposing (onClick)
import Task
import Time exposing (Time)
main =
program { init = (Model 0 0, Cmd.none), view = view, update = update, subscriptions = (/_ -> Sub.none) }
type alias Model =
{ count: Int
, updateTime : Time
}
view model =
Html.App.map GetTimeAndThen (modelView model)
type Msg
= GetTimeAndThen ModelMsg
| GotTime ModelMsg Time
update msg model =
case msg of
GetTimeAndThen wrappedMsg ->
(model, Task.perform (/_ -> Debug.crash "") (GotTime wrappedMsg) Time.now)
GotTime wrappedMsg time ->
let
(newModel, cmd) = modelUpdate wrappedMsg time model
in
(newModel, Cmd.map GetTimeAndThen cmd)
type ModelMsg = Increment | Decrement
modelUpdate msg time model =
case msg of
Increment ->
({model | count = model.count + 1, updateTime = time}, Cmd.none)
Decrement ->
({model | count = model.count - 1, updateTime = time}, Cmd.none)
modelView model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (toString model.count) ]
, button [ onClick Increment ] [ text "+" ]
, div [] [ text (toString model.updateTime) ]
]
Ya había hecho esta pregunta:
¿Cómo obtengo la hora actual en Elm?
Y lo respondí escribiendo mi propia variante ( ahora obsoleta ) de la aplicación de inicio:
http://package.elm-lang.org/packages/z5h/time-app/1.0.1
Por supuesto, la arquitectura Elm ha cambiado desde entonces, y mi vieja forma de hacer las cosas ya no funciona, porque no hay señales o Time.timestamp
.
Asi que....
Supongamos que construyo una aplicación con la firma de la función de actualización estándar:
update : Msg -> Model -> (Model, Cmd Msg)
Me gustaría marcar el tiempo de mi modelo con el tiempo en la actualización. Una casi inaceptable solución es suscribirse a Time.every
. Conceptualmente esto no es lo que quiero. Esto es actualizar el modelo con el tiempo y también actualizar por separado el modelo con mensajes.
Lo que quiero es poder escribir una función de actualización con firma:
updateWithTime : Msg -> Time -> Model -> (Model, Cmd Msg)
Empecé a tratar de resolver esto agregando algunos mensajes adicionales:
Msg = ... When | NewTime Time
Y creando un nuevo comando:
timeCmd = perform (/x -> NewTime 0.0) NewTime Time.now
Entonces, en cualquier acción, puedo disparar un comando extra para recuperar el tiempo. Pero esto se vuelve desordenado y fuera de control rápidamente.
¿Alguna idea sobre cómo puedo limpiar esto?
He encontrado lo que creo que es una solución más elegante que la respuesta aceptada. En lugar de tener dos modelos separados, el mensaje GetTimeAndThen
contiene un controlador que devuelve un mensaje. El código se siente mucho más natural y similar a un olmo, y se puede usar de una manera más general:
module Main exposing (..)
import Html exposing (div, button, text)
import Html.App as App
import Html.Events exposing (onClick)
import Task
import Time exposing (Time)
main =
App.program
{ init = ( Model 0 0, Cmd.none )
, view = view
, update = update
, subscriptions = (/_ -> Sub.none)
}
view model =
div []
[ button [ onClick decrement ] [ text "-" ]
, div [] [ text (toString model) ]
, button [ onClick increment ] [ text "+" ]
]
increment =
GetTimeAndThen (/time -> Increment time)
decrement =
GetTimeAndThen (/time -> Decrement time)
type Msg
= Increment Time
| Decrement Time
| GetTimeAndThen (Time -> Msg)
type alias Model =
{ count : Int, updateTime : Time }
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GetTimeAndThen successHandler ->
( model, (Task.perform assertNeverHandler successHandler Time.now) )
Increment time ->
( { model | count = model.count + 1, updateTime = time }, Cmd.none )
Decrement time ->
( { model | count = model.count - 1, updateTime = time }, Cmd.none )
assertNeverHandler : a -> b
assertNeverHandler =
(/_ -> Debug.crash "This should never happen")
Después de una discusión sobre esta pregunta en Slack, aquí hay una implementación alternativa sin funciones en el Msg
. Al igual que con la respuesta aceptada, el modelo solo se actualiza cuando la Task
Time.now
tiene éxito.
import Html exposing (div, button, text)
import Html.App as App
import Html.Events exposing (onClick)
import Task
import Time exposing (Time)
main =
App.program
{ init = init
, view = view
, update = update
, subscriptions = (/_ -> Sub.none)
}
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (toString model) ]
, button [ onClick Increment ] [ text "+" ]
]
type Msg
= NoOp
| Increment
| Decrement
| GetTimeSuccess Msg Time
| GetTimeFailure String
type alias Model =
{ count : Int, updateTime : Result String Time }
init : (Model , Cmd Msg)
init =
( { count = 0
, updateTime = Err "No time yet!"
}
, Task.perform GetTimeFailure (GetTimeSuccess NoOp) Time.now
)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp -> (model, Cmd.none)
Increment ->
( model
, Task.perform GetTimeFailure (GetTimeSuccess Increment) Time.now
)
Decrement ->
( model
, Task.perform GetTimeFailure (GetTimeSuccess Decrement) Time.now
)
GetTimeSuccess Increment time ->
( { model | count = model.count + 1, updateTime = Ok time}
, Cmd.none
)
GetTimeSuccess Decrement time ->
( { model | count = model.count - 1, updateTime = Ok time}
, Cmd.none
)
GetTimeSuccess _ time ->
( { model | updateTime = Ok time}
, Cmd.none
)
GetTimeFailure msg ->
( { model | updateTime = Err msg}
, Cmd.none
)
Puede crear un módulo nativo y luego exponer una función de timestamp
que obtiene el tiempo de Date.now()
en JavaScript.
Esto es más o menos lo que se vería así:
Timestamp.elm
module Timestamp exposing (timestamp)
import Native.Timestamp
timestamp : () -> Int
timestamp a = Native.Timestamp.timestamp a
Native / Timestamp.js
var _YourRepoUserName$your_repo$Native_Timestamp = function() {
return { timestamp: function(a) {return Date.now()}
}
Main.elm
port module Main exposing (..)
import Timestamp exposing (timestamp)
luego puede usar ( timestamp ()
) en cualquier parte de Elm para obtener la marca de tiempo actual como Int.
Nota: utilicé la timestamp : () -> Int
porque no pude hacer que funcione de otra manera. timestamp : Int
simplemente devolvió el tiempo codificado de la primera carga.
Avíseme si esto podría mejorarse.
Tengo una respuesta a mi propia pregunta (basada en una sugerencia de amilner42). Estoy usando esta solución en mi código actual.
Me gusta mucho la solución de @ w.brian, pero las funciones en los mensajes rompen el depurador.
Me gusta la solución de @robertjlooby, y esto es muy similar, aunque elimina un tipo adicional y se actualiza para 0.18.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp ->
model ! []
TickThen msg ->
model ! [ Task.perform (Tock msg) Time.now ]
Tock msg time ->
updateTimeStampedModel msg { model | time = time }
otherMsg ->
update (TickThen msg) model
updateTimeStampedModel : Msg -> Model -> ( Model, Cmd Msg )
updateTimeStampedModel msg model =
case msg of
NoOp ->
update msg model
TickThen _ ->
update msg model
Tock _ _ ->
update msg model
-- ALL OTHER MESSAGES ARE HANDLED HERE, AND ARE CODED TO ASSUME model.time IS UP-TO-DATE.
olmo-0.18 ejemplo completo https://runelm.io/c/72i
import Time exposing (Time)
import Html exposing (..)
import Html.Events exposing (onClick)
import Task
type Msg
= GetTime
| NewTime Time
type alias Model =
{ currentTime : Maybe Time
}
view : Model -> Html Msg
view model =
let
currentTime =
case model.currentTime of
Nothing ->
text ""
Just theTime ->
text <| toString theTime
in
div []
[ button [ onClick GetTime ] [ text "get time" ]
, currentTime
]
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GetTime ->
model ! [ Task.perform NewTime Time.now ]
NewTime time ->
{ model | currentTime = Just time } ! []
main : Program Never Model Msg
main =
program
{ init = init
, update = update
, view = view
, subscriptions = always Sub.none
}
init : ( Model, Cmd Msg )
init =
{ currentTime = Nothing } ! []