【问题标题】:How do I get the current time in Elm 0.17/0.18?如何获取 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

所以在任何操作中,我都可以触发一个额外的命令来检索时间。但这很快就会变得混乱和失控。

关于如何清理它的任何想法?

【问题讨论】:

    标签: functional-programming elm frp


    【解决方案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")
    

    【讨论】:

    • 我只是重构了一些代码来使用这种风格并且更喜欢它。设置为接受的答案。
    • 虽然我真的很喜欢这个答案,但我会取消选择它作为正确答案。原因是,如果应用程序的模型和消息是可序列化的,则可以记录和回放应用程序的历史(对于时间旅行调试器很重要)。函数不可序列化,因此在模型中包含函数或动作不能与时间旅行调试器一起使用。
    • 这是一个有趣的想法,也是我从未考虑过的。我应该注意到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 } ! []
    

    【讨论】:

      【解决方案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 只是返回了首次加载的硬编码时间。

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

        【讨论】:

          猜你喜欢
          • 2015-06-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-12-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多