конспект

шаблон программы


type alias Model = ...

type Msg = ...
      
init : (Model , Cmd Msg)

view : Model -> Html Msg

update : Msg -> Model -> (Model , Cmd Msg)

subs : Model -> Sub a

main : Program Never
main = Html.App.program
  { init = init
  , update = update
  , view = view
  , subscriptions = subs
  }

функция subscriptions, исходя из текущего состояния модели, говорит, какие внешние события нам интересно слушать, и тип ее ответа параметризован типом наших сообщений, т.к. внешние события приходят в рамках все того же потока сообщений, а когда подписываешься на внешнее событие, говоришь, как его завернуть в твой тип сообщений

функция update получает все сообщения твоего типа и меняет модель, а заодно может "выдать команду" - указание рантайму произвести некоторый эффект, подобно значению типа IO Smthng в хаскеле

 

 

 

* * *

Elm has managed effects: things like HTTP requests or writing to disk are all treated as data

type Cmd msg

every Cmd specifies

  1. which effects you need access to
  2. the type of messages that will come back into your app

 

Cmd.map : (a -> b) -> Cmd a -> Cmd b

Cmd.batch : List (Cmd a) -> Cmd a

Cmd.none : Cmd msg

* * *

a subscription is a way of telling Elm, “Hey, let me know if anything interesting happens over there!”

type Sub msg

every Sub specifies

  1. which effects you need access to
  2. the type of messages that will come back into your application

 

Sub.map : (a -> b) -> Sub a -> Sub b

Sub.batch : List (Sub a) -> Sub a

Sub.none : Sub a

 

 

 

пример с мышью

Sub, Cmd, List экспортируются автоматом

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

---------------------------

module Main exposing (..)

import Html.App
import Child exposing (update, view, Msg, Model)
import Mouse exposing (clicks, Position)

subs : Model -> Sub Msg
subs m = List.map clicks [Child.P , Child.G] |> Sub.batch 

init : (Model , Cmd Msg)
init = ([] , Cmd.none)
       
main : Program Never
main = Html.App.program
  { init = init
  , update = update
  , view = view
  , subscriptions = subs
  }         

----

module Child exposing (view, update, Model, Msg(..))

import Html exposing (Html , div , p , span , text , table , tr , td)
import Html.Attributes exposing (style)
import Mouse exposing (Position)
import Random exposing (generate , int)

type Msg = P Position | G Position | R Int

type alias Model = List Position
       
view : Model -> Html Msg
view m = div [] [ p [] [ span [] [text (toString m)] ]
                , table [style [("background","orange"),("margin","100px")]] (g m)
                ]
         
g : Model -> List (Html a) 
g ps = List.map (\{x , y} ->
         tr []
           [ td [] [text (toString x)]
           , td [] [text (toString y)]
           ]) ps

update : Msg -> Model -> (Model, Cmd Msg)
update t m = case t of
  P p -> (p :: m , Cmd.none)
  G p -> (m , generate R (int p.x p.y))
  R r -> ({x = r , y = r} :: m , Cmd.none)   

 

 

 

Task

Task describes asynchronous effect that may fail

type alias Task error result = Task error result

say we have a task with the type

Task String User

this means that when we perform the task, it will
  • either fail with a String message or
  • succeed with a User result
  • so this could represent a task that is asking a server for a certain user and returning some err-message in case of fiasco

    Task.succeed : a -> Task x a
    a task that succeeds immediately

    Task.fail : x -> Task x a
    a task that fails immediately

    Task.map : (a -> b) -> Task x a -> Task x b

     

    the only way to do things in Elm is to give commands to the Elm runtime

    Task.perform : (a -> msg) -> Task Never a -> Cmd msg

    пример с таймером

    module Main exposing (..)
    
    import Html.App
    
    import Task exposing (perform)
    import Time exposing (Time, now, every, second)
    
    import Html exposing (button, text, p, div, Html)
    import Html.Events exposing (onClick)
    
    
    type Msg = A Time | B Time | C
    
    type alias Model = { curr : Time , stor : Time }
    
    
    init = ({ curr = 617 , stor = 23 } , Cmd.none)
    
    update : Msg -> Model -> ( Model, Cmd Msg )
    update a m = case a of
      C ->   (  m             , Task.perform A B now )
      A t -> ( {m | curr = t} , Cmd.none )
      B t -> ( {m | stor = t} , Cmd.none )
    
    view : Model -> Html Msg
    view m =
      div [] [ button [onClick C] [text "нажми меня"]
             , p [] [text (toString m.curr)]
             , p [] [text (toString m.stor)]
             ]
    
    subs _ =  Time.every second  A 
    
    
    main : Program Never
    main = Html.App.program
      { init = init
      , update = update
      , view = view
      , subscriptions = subs
      }
    

     

     

     

    Ports

    unlike ClojureScript whose philosophy is to embrace the underlying host environment, Elm abstracts it away. it's not gone completely it's just not easy to access

    the key is to using Ports. a Port acts as a bridge between Elm and JavaScript. they either go in to Elm or out of Elm

    TODO

     

     

     

    GET

    пример с получением текстового файла

    module Txt exposing (main)
        
    import Html exposing (..)
    import Html.Events exposing (onClick)
    import Http
    import Html.App 
    import Task
    import Random exposing (generate , int)
    
    
    main = Html.App.program
      { init = ("" , Cmd.none) 
      , view = view
      , update = update
      , subscriptions = \_ -> Sub.none
      }
    
    
    type alias Model = String
    
    type Msg = Less Int | More | Fail Http.Error | Succ String
    
    
    mylist : List String    
    mylist = ["Maybe.txt" , "Process.txt" , "Docs.txt" , "Task.txt" , "Result.txt"]
             
    update : Msg -> Model -> (Model , Cmd Msg)
    update a m =
      case a of
        More -> (m , generate Less (int 0 4))
        Less n -> (m , (Maybe.withDefault "Docs.txt" (List.drop n mylist |> List.head) |> fetchIt))
        Succ x -> (x , Cmd.none)
        Fail _ -> (m , Cmd.none)
    
    view : Model -> Html Msg
    view m =
      div []
        [ button [ onClick More ] [ text "еще парочку!" ]
        , br [] []
        , pre [] [text m]
        ]
    
    fetchIt : String -> Cmd Msg   
    fetchIt m =
      let url = "http://localhost:8000/txts/" ++ m 
      in Task.perform Fail Succ (Http.getString url)
    

     

     

     

    пример с изображениями

    module Pic exposing (main)
    
    import Html exposing (..)
    import Html.Events exposing (onClick)
    import Html.Attributes exposing (src)
    import Html.App 
    import Random exposing (generate , int)
    
    main = Html.App.program
      { init = ("pic00.png" , Cmd.none) 
      , view = view
      , update = update
      , subscriptions = \_ -> Sub.none
      }
    
    
    type alias Model = String
    
    type Msg = Less Int | More 
    
    
    mylist : List String    
    mylist = ["pic00.png" , "pic01.png", "pic02.png", "pic03.png"]
    
    
    update : Msg -> Model -> (Model , Cmd Msg)
    update a m =
      case a of
        More -> (m , generate Less (int 0 3))
        Less n -> (f n , Cmd.none)
    
    view : Model -> Html Msg
    view m =
      div []
        [ button [ onClick More ] [ text "еще парочку!" ]
        , br [] []
        , img [src ("http://localhost:8000/pics/" ++ m)] []
        ] 
    
    f : Int -> Model
    f n = Maybe.withDefault "pic00.png" (List.drop n mylist |> List.head)
    

     

     

    from Http module docs

    
    post : Decoder v -> String -> Body -> Task Error v
    
    
    send : Settings -> Request -> Task RawError Response
    
    
    type alias Settings = 
        { timeout : Time 
        , onStart : Maybe (Task () ())
        , onProgress : Maybe (Maybe { loaded : Int, total : Int } -> Task () ()) 
        , desiredResponseType : Maybe String 
        , withCredentials : Bool
        }
    
    type Body
    
    empty : Body
    
    string : String -> Body
    
    type alias Request = 
      { verb : String 
      , headers : List (String, String) 
      , url : String 
      , body : Body
      }
    
    
    type Value = Text String | Blob Blob
                               
    type alias Response = 
        { status : Int
        , statusText : String 
        , headers : Dict String String 
        , url : String 
        , value : Value 
        }
    

    пример с отправкой методом Post

    module Post exposing (..)
    
    import Html exposing (..)
    import Html.App
    import Html.Attributes exposing (..)
    import Html.Events exposing (onClick)
    import Http exposing (..)
    import Json.Decode  as JD exposing (list , string)
    import Task
    import Json.Encode as JE exposing (..)
    
    main : Program Never
    main = Html.App.program
      { init = ([6,7,8,9,23] , Cmd.none)
      , update = update
      , view = view
      , subscriptions = \_ -> Sub.none
      }
        
    type Msg = A Response | B Model | C | D | E RawError | F Error 
    
    type alias Model = List Int
    
    view : Model -> Html Msg
    view m =
        div []
            [ p [] [text (toString m)]
            , button [onClick C] [text "post it"]
            , button [onClick D] [text "send it"]            
            ]
    
    update : Msg -> Model -> (Model, Cmd Msg)
    update t m = case t of
        A v -> (m , Cmd.none)             
        D -> (m , Task.perform E A (sendIt m)) -- send List Int as JSON {[x,y,...]}                
        C -> (m , Task.perform F B (postIt m)) -- send List Int as params where key is index 
        B v -> (m , Cmd.none)
        F v -> (m , Cmd.none)
        E v -> (m , Cmd.none)       
    
    postIt : Model -> Platform.Task Error Model  
    postIt m =
        let serial = List.map JE.int m |> JE.list |> JE.encode 0 
        in Http.post   -- : Decoder v -> String -> Body -> Task Error v
             (JD.list JD.int)
             "http://localhost:8000"
             (Http.string serial) 
             
    sendIt : Model -> Platform.Task Http.RawError Http.Response 
    sendIt m =
       let serial = List.map JE.int m |> JE.list |> JE.encode 0 
       in    
         Http.send   -- : Settings -> Request -> Task RawError Response 
           Http.defaultSettings    
           { verb = "POST"
           , headers = []
           , url = "http://localhost:8000"
           , body = "{" ++ serial ++ "}" |> Http.string  
           } 
    

     

     

     

    Process

    TODO