Как получить текущее время в Elm 0.17/0.18?

Я уже задал этот вопрос:
Как получить текущее время в Elm?

И ответил на это, написав мой собственный (теперь устаревший) вариант start-app:
http://package.elm-lang.org/packages/z5h/time-app/1.0.1

Конечно, архитектура Вяз изменилась, и мой старый способ делать вещи больше не работает, потому что нет сигналов или Time.timestamp.

Так....

Предположим, что я создаю приложение со стандартной сигнатурой функции обновления:
update : Msg -> Model -> (Model, Cmd Msg)

Я хотел бы создать временную метку моей модели со временем при обновлении. Одно неприемлемое решение - подписаться на Time.every. Концептуально это не то, что я хочу. Это обновляет модель со временем, а также отдельно обновляет модель с сообщениями.

Что я хочу, так это написать функцию обновления с подписью:
updateWithTime : Msg -> Time -> Model -> (Model, Cmd Msg)


Я начал пытаться решить эту проблему, добавив дополнительные сообщения:
Msg = ... When | NewTime Time

И создание новой команды:
timeCmd = perform (\x -> NewTime 0.0) NewTime Time.now

Таким образом, в любом действии я могу запустить дополнительную команду для получения времени. Но это становится беспорядочным и из рук в руки.

Любые идеи о том, как я могу это очистить?

Ответы

Ответ 1

Один из вариантов без необходимости делать выборку времени для каждого пути обновления - это обернуть ваш Msg в другой тип сообщения, который будет извлекать время, а затем вызвать ваш обычный update с указанием времени. Это измененная версия http://elm-lang.org/examples/buttons, которая обновляет временную метку модели при каждом обновлении.

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

Ответ 2

Я нашел то, что считаю более элегантным, чем принятый ответ. Вместо двух отдельных моделей сообщение GetTimeAndThen содержит обработчик, который возвращает сообщение. Код выглядит намного более естественным и похожим на вяза, и его можно использовать более общим образом:

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

Ответ 3

elm-0.18 полный пример 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 } ! []

Ответ 4

После обсуждения этого вопроса на Slack, здесь есть альтернативная реализация без функций в Msg. Как и в случае принятого ответа, модель обновляется только тогда, когда успешно завершается Time.now Task.

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
            )

Ответ 5

У меня есть ответ на мой собственный вопрос (на основе предложения amilner42). Я использую это решение в своем текущем коде.

Мне очень нравится решение от @w.brian, но функции в сообщениях прерывают отладчик.
Мне нравится решение от @robertjlooby, и это очень похоже, хотя оно устраняет лишний тип и обновляется на 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.

Ответ 6

Вы можете создать собственный модуль, а затем выставить функцию timestamp, которая получает время от Date.now() в JavaScript.

Это примерно то, на что это будет выглядеть:

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)

то вы можете использовать (timestamp ()) в любом месте в Elm, чтобы получить текущую метку времени как Int.


Примечание. Я использовал timestamp : () -> Int, потому что я не мог заставить его работать иначе. timestamp : Int просто вернул твердое время первой загрузки.

Сообщите мне, если это можно улучшить.