Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Program Combinator
module ProgramCombinator exposing (..)
import Html exposing (Html)
import Time exposing (Time, every, second)
-- Example
type alias Model =
{ message : String
}
prog1 : HeadlessProgramWithChannel {} String String Never
prog1 =
{ init = ( {}, Cmd.none )
, subscriptions = \_ -> every second (\time -> (toString time))
, update = \time -> \model -> ( model, Cmd.none, [ time ] )
, receive = \_ -> \model -> model
}
prog2 : HtmlProgramWithChannel Model Never Never String
prog2 =
{ init = ( { message = "" }, Cmd.none )
, subscriptions = \_ -> Sub.none
, update = \_ -> \model -> ( model, Cmd.none, [] )
, view = \model -> Html.text <| "message: " ++ model.message
, receive = \message -> \model -> { model | message = message }
}
main =
combineHeadlessAndHtmlWithChannel prog1 prog2 |> Html.program
-- Program convolution
swap : ( a, b ) -> ( b, a )
swap ( a, b ) =
( b, a )
type alias HeadlessProgramWithChannel model msg snd recv =
{ init : ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg, List snd )
, subscriptions : model -> Sub msg
, receive : recv -> model -> model
}
type alias HtmlProgramWithChannel model msg snd recv =
{ init : ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg, List snd )
, subscriptions : model -> Sub msg
, view : model -> Html msg
, receive : recv -> model -> model
}
type alias HeadlessProgram model msg =
{ init : ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
}
type alias HtmlProgram model msg =
{ init : ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
, view : model -> Html msg
}
type Msg a b
= AMsg a
| BMsg b
{-| Combines a headless program with a
-}
combineHeadlessAndHtmlWithChannel :
HeadlessProgramWithChannel modela msga send recv
-> HtmlProgramWithChannel modelb msgb recv send
-> HtmlProgram ( modela, modelb ) (Msg msga msgb)
combineHeadlessAndHtmlWithChannel progA progB =
{ init = init progA progB
, update = update progA progB
, subscriptions = subscriptions progA progB
, view = view progB
}
combineHeadlessWithChannel :
HeadlessProgramWithChannel modela msga send recv
-> HeadlessProgramWithChannel modelb msgb recv send
-> HeadlessProgram ( modela, modelb ) (Msg msga msgb)
combineHeadlessWithChannel progA progB =
{ init = init progA progB
, update = update progA progB
, subscriptions = subscriptions progA progB
}
{-| Combines the init fields of two programs.
-}
init :
{ a | init : ( modela, Cmd msga ) }
-> { b | init : ( modelb, Cmd msgb ) }
-> ( ( modela, modelb ), Cmd (Msg msga msgb) )
init progA progB =
let
modelA =
Tuple.first progA.init
modelB =
Tuple.first progB.init
cmdA =
Tuple.second progA.init
cmbB =
Tuple.second progB.init
in
( ( modelA, modelB )
, Cmd.batch
[ Cmd.map AMsg cmdA
, Cmd.map BMsg cmbB
]
)
{-| Combines the update functions of two programs, with receive channels.
-}
update :
{ a | receive : recv -> modela -> modela, update : msga -> modela -> ( modela, Cmd msga, List snd ) }
-> { b | receive : snd -> modelb -> modelb, update : msgb -> modelb -> ( modelb, Cmd msgb, List recv ) }
-> Msg msga msgb
-> ( modela, modelb )
-> ( ( modela, modelb ), Cmd (Msg msga msgb) )
update progA progB msg model =
let
modelA =
Tuple.first model
modelB =
Tuple.second model
updateAndSend progA progB msg modelA modelB tagger =
let
( newModel, cmds, sendItems ) =
progA.update msg modelA
in
( ( newModel
, List.foldl (progB.receive) modelB sendItems
)
, Cmd.map tagger cmds
)
in
case msg of
AMsg amsg ->
updateAndSend progA progB amsg modelA modelB AMsg
BMsg bmsg ->
updateAndSend progB progA bmsg modelB modelA BMsg
|> Tuple.mapFirst swap
{-| Combines the subscriptions of two programs.
-}
subscriptions :
{ a | subscriptions : modela -> Sub msga }
-> { b | subscriptions : modelb -> Sub msgb }
-> ( modela, modelb )
-> Sub (Msg msga msgb)
subscriptions progA progB model =
let
modelA =
Tuple.first model
modelB =
Tuple.second model
in
Sub.batch
[ progA.subscriptions modelA |> Sub.map AMsg
, progB.subscriptions modelB |> Sub.map BMsg
]
{-| Lifts the view of one program as the view of a combined program.
-}
view :
{ b | view : modelb -> Html msgb }
-> ( modela, modelb )
-> Html (Msg msga msgb)
view progB model =
let
modelB =
Tuple.second model
in
progB.view modelB |> Html.map BMsg
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment