Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Elm, is there a way to merge union types ? (for modularity purpose)

Starting from those three declarations :

type SharedMsg
   = SharedAction

type Page1Msg
   = Page1Action

type Page2Msg
   = Page2Action

I there a way to obtain an equivalent of the following one? Like a way to "merge" union types ?

type Msg
   = SharedAction
   | Page1Action
   | Page2Action

=============================

Context : I am splitting an Elm application into one module per page with their own folders.

Some actions will be shared, and some actions will be page-specific.

If I was to use the Html.map method, I feel that I would have to re-write every shared action that a page uses in its own PageMsg message type:

type Page1Msg
   = Page1Action
   | SharedAction

type Msg
   = Page1Msg Page1Msg
   | Page2Msg Page2Msg

view : Model -> Html Msg
view =
   Html.map Page1Msg (Page1View.view model)

Hence my thinking of using a unique Msg type for all pages, but preserving modularity by writing page-specific messages in their own folders, and then somehow defining a unique Msg type by merging them.

like image 334
AlexHv Avatar asked May 11 '17 10:05

AlexHv


2 Answers

@z5h's answer is almost correct, but the type constructors have to have different names.

You can't merge the types the way you'd like to.

As for the idiomatic way: You would name the split types just Msg, not Page1Msg. So, for example:

Page1.elm:

module Page1 exposing (Msg)

type Msg
  = Foo

Page2.elm:

module Page2 exposing (Msg)

type Msg
  = Bar

Shared.elm:

module Shared exposing (Msg)

type Msg
  = Baz

Main.elm:

module Main exposing (..)

import Shared
import Page1
import Page2

type Msg
  = SomethingCustom
  | SharedMsg Shared.Msg
  | Page1Msg Page1.Msg
  | Page2Msg Page2.Msg

By the way, remember that if you split the modules into Page1.View, Page1.Types, etc., then as long as the exposed functions don't overlap, you can import different modules under the same name, ie:

import Page1.Types as Page1
import Page1.State as Page1
import Page1.View as Page1
import Page1.Decoders as Page1
like image 124
Martin Janiczek Avatar answered Sep 24 '22 23:09

Martin Janiczek


Do not forget that you are absolutely not obliged to follow the update-view definitions exactly as in the basic examples. In your case, you could adapt the update function to your needs

how about in parent:

update message model = 
    let 
        sharedMsgs = 
            { msg1 = Msg1 
            , msg2 = Msg2 
            }

    in case message of
        Page1Msg msg ->
            let (m, c) =
                update sharedMsgs msg model.page1
            in case c of 
                Nothing ->
                    m 
                Just c ->
                    update c m

where the update function in page1 has signature

update : SharedMessages msg -> Msg -> Page1Model -> (Page1Model, Maybe msg)
like image 33
Simon H Avatar answered Sep 24 '22 23:09

Simon H