DEV Community

The basic Elm example that I wish I'd had

Nimmo on July 20, 2018

Note: You don't need very much Elm knowledge at all to benefit from this, but equally this isn't really intended for anyone with zero knowledge of ...
Collapse
 
kwitgit profile image
kwitgit

Great post! So after this exercise, do you think making Model into a pure Custom Type is the way to go for larger projects? Or is this only suitable for small demos? (As opposed to the "traditional" Elm Model, which is a record made up of many types and custom types.)

Collapse
 
nimmo profile image
Nimmo • Edited

Slight update: After adding navigation into another application, which meant needing to add a navigation key, I've ended up re-evaluating this at the moment. Although all I've done is added any globally-available info to the model, and kept the state the way I described earlier:

-- MODEL


type alias Model =
    { key : Nav.Key
    , state : State
    }


type State
    = ViewSignUp SignUpData
    | ViewLogin LoginData
    | ViewPasswordReset PasswordResetData
    | Loading

And it's still the state that drives the UI itself:

-- VIEW


view : Model -> Document Msg
view model =
    case model.state of
        Loading ->
            { title = "Some app"
            , body = [ loadingView ]
            }

        ViewSignUp data ->
            let
                signUpView =
                    Html.map (\x -> SignUpMsg x) <| SignUp.view data
            in
            { title = "Sign up to Some app"
            , body = [ signUpView ]
            }

        ViewLogin data ->
            let
                loginView =
                    Html.map (\x -> LoginMsg x) <| Login.view data
            in
            { title = "Log in to Some app"
            , body = [ loginView ]
            }

        ViewPasswordReset data ->
            let
                forgottenPasswordView =
                    Html.map (\x -> PasswordResetMsg x) <| PasswordReset.view data
            in
            { title = "Request a password reset"
            , body = [ forgottenPasswordView ]
            }

Collapse
 
kwitgit profile image
kwitgit

I do like this better. Somehow it feels a little "forced" to make the model as one giant custom type. With this type of edit, you can still model your door/alarm states elegantly and completely with a big custom type, and get all the benefits of that. But other stuff in the model (like navigation key or login status) that doesn't have anything to do with the door state can live separately, as a different piece of the model record.

Collapse
 
nimmo profile image
Nimmo • Edited

Thanks! <3

I'm by no means an expert in Elm, so it's difficult for me to say for certain (although I'm currently working on a larger Elm application and I'm sure I'll come out of that with more thoughts!), but my current thought on this is that yes, I do prefer the idea of the Model being a Custom Type as opposed to a record. The main thing I kept coming up against when having the model be a record was that it seemed like every separate view ended up having information (or at least theoretical access to information) that it just didn't need - it could very well be that I wasn't organising my models very well, but since making this change in my own code things have felt easier to deal with and reason about.

Again, my thoughts on this in future might change, but as of right now (which is after all when you're asking :D ) I think that this approach is helpful for reasoning about the application itself - if your Model is always describing the state of your application, then it feels likely that this will make life easier for anyone maintaining your application in the future - something I think we should all be careful to consider.

Collapse
 
kwitgit profile image
kwitgit

Also, looking at it more carefully... how is the top-level DisplayingRoom type defined? I'm sure that's a ridiculously basic question...

Collapse
 
nimmo profile image
Nimmo • Edited

Ah I think my wording has probably confused matters - Model is the custom type, DisplayingRoom and Failure are values that custom type can have. (These are known as type variants)

DoorState and AlarmState are also custom types.

This might help clarify things a little:

Consider Bool - that is a type that can have a value of either True or False, and would be represented (and I imagine probably actually is represented in the source code!) as:

type Bool 
  = True
  | False

Does that answer your question? :)

Thread Thread
 
kwitgit profile image
kwitgit

Ooohh yes, that makes perfect sense! A custom type can have any... custom... values you make up, they don't have to be defined separately anywhere else. I get it now. My mental block was that I was still thinking of the Model type as a record, just because it was named Model. LOL.

I guess I'm starting to see why "custom types" is a better name than "union types" (what they used to be in 0.18). Thanks for the update to 0.19!

Thread Thread
 
nimmo profile image
Nimmo

Glad that helped! I can totally see how that caused confusion.

And yeah, I think the change of naming convention from union to custom is a big positive. Custom type is a phrase that can be easily understood without even having any real understanding of the language at all! :)

Collapse
 
ben profile image
Ben Halpern

Interestingly I'd specifically been thinking a lot about modeling room states while wiring up a little home automation app. And I'd been considering Elm for the interface but needed a bit of help thinking through it. Soooo thanks. 😄

Collapse
 
nimmo profile image
Nimmo

Hah, glad to be of service. Hope this ends up being useful for you then - keep me posted!

Collapse
 
joshcheek profile image
Josh Cheek

You should be to syntax highlight the source code like this:

source code that gets syntax highlighted

update msg model =
  case model of
    ViewRoom doorState alarmState ->
Collapse
 
nimmo profile image
Nimmo

Ah, I didn't realise this at all. Thanks for the tip!

Collapse
 
pasdut profile image
Pascal

Hi Nimmo,

We are now more than a year further of your original post. Do you still use the same structure? Or did you find another way to organize (after gaining more experience)?

Collapse
 
pasdut profile image
Pascal

Strange...it says I posted this in dec 18 while we are dec 19

Collapse
 
nimmo profile image
Nimmo

Hey, yeah I'm still doing this (i.e., this way: dev.to/nimmo/comment/6i4n ), and have been full-time in production for months now. It's really nice!

I have an overall model in Main which is a record, that has a state, and my states in Main tend to be things like ViewingPageX PageX.Model | ViewingPageY PageY.Model etc. etc., and then PageX.Model and PageY.Model would either just be a custom type that defined the states of their own pages, or they might also be a record if there's some info that needs to be available in every state (like, for example, an environment definition or something).

Does that help? :)

Thread Thread
 
pasdut profile image
Pascal

Thanks for your feedback. This certainly helps. I like well structured code and like to learn from people more experienced with elm. Tutorials only cover small stuff...

Collapse
 
nimmo profile image
Nimmo

Also I think the Dec 18 on the comment is because it is the 18th of December, not December 2018! :D

Thread Thread
 
pasdut profile image
Pascal

Of course, stupid me...

Thread Thread
 
nimmo profile image
Nimmo

Not at all! Incredibly easy mistake to have made, just happened to be literally the only day of the year that it would have happened. :D

Collapse
 
1hko profile image
1hko

Nice model. It even avoids a problem found in some real-life doors: 1. open the the door, 2. toggle the deadbolt lock, 3. close the door, 4. "error: cannot close locked door"

Collapse
 
nimmo profile image
Nimmo

Ha, yes! That's a perfect example of a state that our application could get into if we hadn't thought about the potential transitions up-front.