如何获取 Elm 0.17/0.18 中的当前时间?

Posted

技术标签:

【中文标题】如何获取 Elm 0.17/0.18 中的当前时间?【英文标题】:How do I get the current time in Elm 0.17/0.18? 【发布时间】:2016-10-27 13:37:51 【问题描述】:

我已经问过这个问题了:How do I get the current time in Elm?

并通过编写我自己(现已弃用)的 start-app 变体来回答它:http://package.elm-lang.org/packages/z5h/time-app/1.0.1

当然,Elm 架构已经改变了,我以前的做事方式不再有效,因为没有信号或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) ]
    ]

【讨论】:

出色的工作!我已经将该示例改编为使用 Elm 0.18,这是一个要点:gist.github.com/r-k-b/e589b02d68cab07af63347507c8d0a2d【参考方案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")

【讨论】:

我只是重构了一些代码来使用这种风格并且更喜欢它。设置为接受的答案。 虽然我真的很喜欢这个答案,但我会取消选择它作为正确答案。原因是,如果应用程序的模型和消息是可序列化的,则可以记录和回放应用程序的历史(对于时间旅行调试器很重要)。函数不可序列化,因此在模型中包含函数或动作不能与时间旅行调试器一起使用。 这是一个有趣的想法,也是我从未考虑过的。我应该注意到there currently is no time-traveling debugger in Elm,尽管它最终会回来。 是的,另一个原因是 Elm 的维护者 Evan Czaplicki 说“Elm 架构的核心规则之一是永远不要将函数放入模型或 Msg 类型中。”。我猜他这么说是因为他对 Elm 的计划有远见。见这里:package.elm-lang.org/packages/evancz/elm-sortable-table/latest【参考方案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  ! []
基于来自http://package.elm-lang.org/packages/elm-lang/core/latest/Task#perform 的示例 https://github.com/knewter/elm-date-playground https://becoming-functional.com/tasks-in-elm-0-18-2b64a35fd82e

【讨论】:

【参考方案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
            )

【讨论】:

这与@rofrol 的回答有何不同?【参考方案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】:

您可以创建一个 Native 模块,然后在 javascript 中公开一个从 Date.now() 获取时间的 timestamp 函数。

这大概是它的样子:

时间戳.elm

module Timestamp exposing (timestamp)

import Native.Timestamp

timestamp : () -> Int
timestamp a = Native.Timestamp.timestamp a

本机/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)

然后你可以在 Elm 的任何地方使用 (timestamp ()) 来获取当前时间戳作为 Int。


注意:我使用了timestamp : () -&gt; Int,因为否则我无法让它工作。 timestamp : Int 只是返回了首次加载的硬编码时间。

让我知道这是否可以改进。

【讨论】:

以上是关于如何获取 Elm 0.17/0.18 中的当前时间?的主要内容,如果未能解决你的问题,请参考以下文章

如何在Elm中获取svg文本的长度

Elm:如何在自己的浏览器窗口中呈现每个 elm 模块? - 同一个应用程序中的两个模块(主模块)

如何在 Elm 中设置焦点?

如何将 elm-monocle 与 sum 类型一起使用?

使用 Elm.Browser.Dom 按类名获取元素

ELM分类基于matlab遗传算法优化ELM神经网络数据分类含Matlab源码 2138期