Elm case matching on complex records

900 views
Skip to first unread message

Eric Kidd

unread,
Dec 1, 2015, 8:38:33 AM12/1/15
to Elm Discuss
Good morning. I'm having a ton of fun with Elm, but I'm back to ask another newbie question, and I'm probably mostly just being confused by my own design mistakes. But here goes.

I'm trying to build an application that works with video. You can find a code snippet here: https://gist.github.com/emk/cee2f4f3873ffc13486a

I have an overly simplistic Model type, just to get started:

type alias Model =
  { errorMessage: Maybe String      -- This is what Rails would call a "flash": we just show it.
  , video: Maybe Video.Model        -- Information about a video and subtitles.
  , player: Maybe VideoPlayer.Model -- Player state: URL, playing/paused, time.
  }

The idea is that the errorMessage represents an application wide error, displayed at the top of the screen if it's present. The Video.Model represents data from my web server: The video's URL, the associated subtitles, and other persistent state. The VideoPlayer.Model represents a generic HTML 5 video player: It has state like { url = "...", playing = true, currentTime = 10.54 }, but it doesn't know anything about my application-specific data.

As a quick-and-dirty first draft, I wanted to write a throwaway view function which worked like this:

view address model =
  case model of
  
    -- If we have an error, show that.
    { errorMessage = Just err } ->
      text err
      
    -- If we have a player, show that.
    { videoPlayer = Just player } ->
      VideoPlayer.view (Signal.forwardTo address VideoPlayerAction) player

    -- Otherwise, just show an error.
    _ -> 
      text "Loading..."

Here, the intent is to say, "If we have an error, just show it. If not, see if we have a player, and show that. Otherwise, show the loading message." However, this is where I get stuck: Elm's record patterns only allow me to match entire fields, and they don't allow me to destructure those fields.

Other functional languages often provide destructuring matches on records. For example, in Rust, I might write:

// A weird struct provided to me by an argument parsing
// library based on 'docopt'.
struct Args {
    cmd_export: bool,
    cmd_csv: bool,
    cmd_review: bool,
    cmd_tracks: bool,
    arg_subs: String,
    // ...
}

// We know that, logically, one of cmd_csv, cmd_review or
// cmd_tracks must be true, but we need to find out which.
fn export_type(args: &Args) -> &str {
    match *args {
        Args{cmd_csv: true, ..} => "csv",
        Args{cmd_review: true, ..} => "review",
        Args{cmd_tracks: true, ..} => "tracks",
        // This is an assertion failure which crashes the application:
        _ => panic!("Cannot determine export type: {:?}", args),
    }
}

Above, I can match on nested record patterns: I can say, "If the record contains the field cmd_csv with a the value true, then do such-and-such."

What's the best way to match nested structure in a record in Elm? I know that with an ADT, I could write "Loaded (Just x) -> ..." and "Loaded Nothing -> ..." in different branches of the same case. But there doesn't seem to be any sort of similar flexibility for records. It's not the sort of thing I'd actually use very often, but on the rare occasions where I do want it, it's really useful.

Now, I can obviously restructure my application to work around this (and I was planning to do anyway, by adding a Flash.Model, Flash.view, etc, as soon as my prototype was working). And of course, I'm prepared to believe that there might be really principled reasons for Elm's design here.

Anyway, I have no idea whether my silly questions are useful or constructive. :-) I think Elm is a really cool language, and I'd love to figure out how to write a well-structured Elm application. Does it help anybody for me to post my confusions and stumbling blocks like this? If all I'm doing is adding noise, I'm happy to bite my tongue. And a big thank you to everybody who's helping create such a cool project.

Iazel

unread,
Dec 1, 2015, 9:04:51 AM12/1/15
to Elm Discuss
Hello, I'm also an elm-newbie, but reading the chapter about contracts, the function signature is like:

isLong : { record | pages : Int } -> Bool

Therefore you could try:
{ errorMessage: Just err } ->

Eric Kidd

unread,
Dec 1, 2015, 9:13:59 AM12/1/15
to Elm Discuss
That would also be a very reasonable syntax, but Elm tells me:

-- SYNTAX PROBLEM ------------------------------------------

I ran into something unexpected when parsing your code!

48│     { errorMessage: Just err } ->
                      ^
I am looking for one of the following things:

    a closing bracket '}'
    a comma ','
    more letters in this name
    whitespace

I've tried a few other less-obvious variations and checked the manual, and I haven't found any way to do this yet.

Looking at another code-base I have available here, I estimate that in other languages with record types and destructuring, I use patterns like this about in about 2% of my case statements, but when I need them, I really do appreciate them.

Anyway, I'll fix this particular quick-and-dirty match to do look more like I ultimately want it to look. I can find solutions to all these problems; I'm just reporting them in case people are interested in where newbies are getting confused. :-)

Ashley Groves

unread,
Dec 1, 2015, 10:11:42 AM12/1/15
to Elm Discuss
Heya! I, too, am an Elm newbie, but perhaps you could expand out the implied nested case statements? E.g.

view address model =
 case model.errorMessage of
   Just err ->
     text err
   Nothing -> case model.videoPlayer of
   Just player ->
     VideoPlayer.view (Signal.forwardTo address VideoPlayerAction) player
   Nothing ->
     text "Loading..."

You're probably looking for a more elegant syntax than that, though.

Peter Damoc

unread,
Dec 1, 2015, 11:58:26 AM12/1/15
to Elm Discuss
Sometimes the best way to match against a record is using multiple parts of it. I've created this silly piece of code to show how you could both match on multiple parts of a record and dive in once you matched something. 

import Html exposing (..) 


type alias VideoModel = 
  { arg1 : Maybe String
  , arg2 : Int }

type alias ErrorModel =
  { msg : String }
  

type alias AppModel = 
  { err : Maybe ErrorModel
  , video : Maybe VideoModel
  }
  

view : AppModel -> Html 
view model =
  case (model.err, model.video) of
    ( Just {msg}, _ ) -> text <| "Here is your error Sir: " ++ msg
    ( Nothing, Nothing ) -> text "No error, no video?" 
    ( Nothing, Just v ) -> 
        case v.arg1 of 
          Nothing -> text <| "what? no argument? "
          Just arg1 -> text <| "Nice vide with arg1: " ++ arg1
        

main = view 
  { err = Nothing
  , video = Just 
    { arg1 = Nothing
    , arg2 = 1
    }
  } 





--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
There is NO FATE, we are the creators.
blog: http://damoc.ro/

Alexey Zlobin

unread,
Dec 2, 2015, 4:30:19 PM12/2/15
to elm-d...@googlegroups.com
Hi Eric,

Record types with more than one Maybe field could hard to understand and use for some. Fortunately Elm has good tools to encode exclusive states in types.

It seems that your case could be modelled well with the Result type [http://package.elm-lang.org/packages/elm-lang/core/3.0.0/Result]. In good old times it used to have a little more explanatory name 'Either', but still we can have quite readable code:

type alias Model = Result (VideoModel, PlayerModel) String

view model =
  case model of
    Ok (video, player) -> Html.text <| "A fine model"
    Err message -> Html.text <| "An error here: " ++ message

Does it fit your problem?

Alexey.
Reply all
Reply to author
Forward
0 new messages