Staying sane with Maybe (maybe vs type)

I’m getting used to Maybe when loading in json from the server, working through Programming Elm’s Photo type, which is a big record that might not exist in the json.

I’m feeling pretty comfortable caseing on Just or Nothing which needs to happen a few times in Msg update branches (we need to unpack and rewrap it in a Just to store in our Model). It uses Maybe.map with an updateRecordField helper function and the maybePhoto to do the business.

Maybe.withDefault is also there to make things easier, but it seems to be for more primitive data types (creating an empty or default Photo record seems pointless).

I’ve got two questions:

  1. It’s sensible to have your main data point as a Maybe in case it’s not there, but silly to have Maybes cluttering up the place.
    • If you don’t have control of the data (user input) how do you normally go about controlling your optional or required data points, so if they’re missing things don’t screw up?
    • If you do have control of your data (admin input) a Maybe seems unnecessary if you’re careful, right?
  2. Maybe.withDefault has a note about not overusing it, and it might be better to use a custom type instead.
    • Are there any good videos or tutorials of a refactor you know of that shows the wrong and right way to do things? Simple explanations if possible.

This post, for example shows a User that has a List friend and requires List.head to edit a friend. List.head also returns a Just a so you’re unpacking two Maybes there.

  • How do you keep sane and the code simple?
  • Where do you avoid using a Maybe type?

Thanks in advance! :slight_smile:

I am not familiar with the refs you mention but would like to chime in with a way of thinking that might benefit you. Model your data in a way that makes sense and the application will in a sense “build it self “. Is it possible for someone to have no friends? Then handle the empty list. If a user in your app must have a friend (possibly because of an invite etc.) then that would be a “non empty list” (modeled by you or an existing package). Using the maybe type is not a sign of bad code but always using it as a replacement for JS undefined is. Does the missing value represent something? Does the missing value mean anything else then Result or your own type might be the way to go.

1 Like

At work we use Maybe.withDefault a lot when mapping over a Maybe a trying to turn it into an HTML string. Something like

model.user
    |> Maybe.map .nickname
    |> Maybe.withDefault ""
    |> Html.text

because you either get a nickname or an empty node. This doesn’t work in every case but I do see it a fair amount.


For custom types instead of Maybe a I’m doing this with some hobby code right now (took a break to type this up). I have a function to search an Array and need to know if I found exactly the thing I’m looking for or approx the thing, or nothing at all. To handle this I have

type FindKeyedShape
    = KeyedShapeAlreadyAtTime
    | InterpolatedShape { shape : Shape, offset : Float, index : Int }
    | NoShapeFound

and can then handle each branch differently.

1 Like

Having a Maybe as the main data in a model is often appropiate. E.g. you didn’t find the user that was requested. It is ok to use Maybe.map a lot in the update function. You can create utility functions that encapsulate that boilerplate.

In your view, you might have a case with two views, one for the empty state and one for the present state. I usually leave Maybe.withDefault until the very end and only if it is really necessary.

1 Like

I don’t think that there are too many issues with having a bunch of Maybes in your model, but very often, if I have a group of them that are required to render a page (or Results/RemoteData etc), I’ll use a custom type for the model because it’s a bit more ergonomic once you have all the data. For example:

type Model
    = LoadingData { mToken : Maybe String, remoteData : RemoteData Http.Error MyPageData }
    | Page { token : String, pageData : MyPageData }
    | FailedToLoadData

view : Model -> Html Msg
view model =
    case model of
        LoadingData {mToken, remoteData } ->
            loadingView mToken remoteData
        Page token pageData ->
            pageView token pageData
        FailedToLoad ->
            failedToLoadView

This way, down in your pageView, you don’t need to check for maybes (or Results or RemoteData etc) everywhere.

IMO, the advice against overusing Maybe is to help make impossible states impossible. If it doesn’t make sense to be able to represent every possible combination of Just and Nothing, you should likely use a custom type.

So just to solidify and condense some of the suggestions on here, it seems to be:

  1. Start with the data (impossible states and make data structures)
  2. Loading states might be best as custom types
  3. Potentially use non-empty lists …
  4. Or default data in your json (instead of a Maybe)? See below
  5. But Maybe types are fine to use, as is Maybe.withDefault.

Maybe.withDefault

@wolfadex I quite like that approach, do you normally just use it for a string or do you also use for other data types?

I understand the general idea or FindKeyedShape, but I can’t quite visualise how that search function might work.

Caseing on a loading type

@ChrisWellsWood I’ve seen this method before of caseing on your model
for noop, loading, loaded, failed which makes sense … but could you expand a little on your custom type records? I understand Page (a title and it’s body), but …

  • Why are you naming things mToken and token? What do they stand for?
  • How do you pass your loaded data to update Page and view when it’s loaded? (I’m assuming MyPageData is the decoder?)

Impossible states

@dta Do you personally have any other examples you could add to that line of thought? Simple ones ideally.

What would a Maybe type look like in the json?

I’m splitting this out into another question, as it brings up the question how to save and represent a type in the json file, be it a Maybe or a type Custom.

We use it for numbers sometimes too, like

model.currencyValue
    |> Maybe.map Currency.format
    |> Maybe.withDefault "-" -- we typically use a dash for Nothing values in tables and lists
    |> Html.text

I won’t go too deep but the gist is I have a List { offset : Float, shape : Shape } and I want to find say the Shape at an offset of 6000ms. I might have an actual Shape at that offset or I might be between 2 instances and want to average them. KeyedShapeAlreadyAtTime says I’m at an instance while InterpolatedShape gives my the interpolated value, and NoShapeFound is for other edge cases.

1 Like

You lost me after this :joy: it doesn’t matter, I get the general idea of using types to represent found, nearlyFound and notFound.

1 Like

One thing that came up recently was

debounce: Boolean
debounceInterval: Maybe Int

This was better as a custom type

type DebounceConfig = NoDebounce | Debounce Int

The original wasn’t exactly two maybes, but the principle is the same. I suppose we could have used one maybe for the solution (our custom type is equivalent to Maybe Int), but the custom type was clearer.

I think you should prefer a Maybe, rather than default data. As default data just hides potential issues and mute errors. I think you should make it obvious that you are missing something, and push it until the very end to deal with that e.g. show an empty view.

Maybes are fine. Your own descriptive custom type is better.
I recommend not reaching for withDefault early. Try to use Maybe.withDefault at the very end, e.g. in the view that needs to show something.

1 Like

@ChrisWellsWood I’ve seen this method before of caseing on your model
for noop, loading, loaded, failed which makes sense … but could you expand a little on your custom type records? I understand Page (a title and it’s body), but …

  • Why are you naming things mToken and token? What do they stand for?
  • How do you pass your loaded data to update Page and view when it’s loaded? (I’m assuming MyPageData is the decoder?)

Sorry, I’ve distracted you with this stuff, they’re all just placeholder names that came off the top of my head. :rofl: mToken was meant to be something like a JWT that the user might need to request something from a server, MyPageData is just the model for your page with whatever stuff you need for it. I omitted the update function, that would change the Model type constructor based on whether the remote data had been received from the server.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.