krisajenkins / remotedata

Tools for fetching data from remote sources (incl. HTTP).
MIT License
249 stars 23 forks source link

Impossible states in update with RemoteData #20

Closed gyzerok closed 6 years ago

gyzerok commented 7 years ago

Let's say for example that we are doing request with optimistic update. In this case our update function might look as follows:

type Msg = SomethingLoaded Something (RemoteData.RemoteData Http.Error Something)

update msg model =
    case msg of
        SomethingLoaded oldSomething webData ->
            case webData of
                RemoteData.NotAsked ->
                    -- this is impossible since after request is finished RemoteData is either Failure or Success

                RemoteData.Loading ->
                    -- this is impossible since after request is finished RemoteData is either Failure or Success

                RemoteData.Failure _ ->
                    -- here I want to restore value from oldSomething

                RemoteData.Success ->
                    -- here I want to put the proper value in place

One might argue here that wildcard could be used to return (model, Cmd.none) in such cases. That is true, however it's moves a person who is looking at this code for the first time into dynamic programming domain. As opposed to static programming it requires him to keep in his brain what and why about this scenario.

Also that goes a bit against popular mantra in Elm community of making impossible states impossible.

The way it could be solved is:

type RemoteData e a
    = NotAsked
    | Loading
    | LoadedData (Loaded e a)

type Loaded e a = Failure e | Success a

Then example will be turned into:

type Msg = SomethingLoaded Something (RemoteData.Loaded Http.Error Something)

update msg model =
    case msg of
        SomethingLoaded oldSomething loaded ->
            case loaded of
                RemoteData.Failure _ ->
                    -- here I want to restore value from oldSomething

                RemoteData.Success ->
                    -- here I want to put the proper value in place

Thoughts?

ericgj commented 7 years ago

I see what you mean, it seems like a fine way to do it. But I think the suggested way of using this library is to store the RemoteData itself in your model, and not extract it from the result (just as you would store a Maybe in other situations where the value may not be present, and not try to default it). So I'm not sure your example presents a typical problem.

You could use RemoteData.mapBoth, and lift your entire model into a RemoteData, or write an updateBoth function to also thread through a Cmd. Not sure if that makes sense though, since you likely want to track the loading status of multiple http requests independently.

Also, just to say it seems a bit dangerous to me to reset your state like that (i.e. wrapping the oldSomething into the SomethingLoaded msg), because another asynchronous Cmd might have changed the state in the meantime.

gyzerok commented 6 years ago

So after some thinking I've realized why was I getting this issues and how to solve them.

When we send an HTTP request we can actually get response consisted only of two states - success and failure. This is exactly what Result type do represent in Elm. And that's exactly what you will get if you will use Http.send.

Also I realized that RemoteData make only sense as some state - not value inside the message. Inside the message in 99% of the cases RemoteData.Loading and RemoteData.NotAsked does not make any sense.

Unfortunately I was misguided by RemoteData.sendRequest. By using it pretty much everywhere in my code I found my self a lot in situations like the one described above.

What seems proper solution to me is to use Http.send and carry Result within your message and then use RemoteData.fromResult in your update function to turn it into RemoteData.

As a conclusion I guess it'll be in Elm spirit to remove RemoteData.sendRequest. Then newbie can hardly make mistake I made in first. Also using Result should provide more clear mental model. At the same time if you want such a helper for whatever reason - it's really easy to define it in your own code.

What do you think @krisajenkins?

gyzerok commented 6 years ago

Humbly ping @krisajenkins

muelli commented 6 years ago

What seems proper solution to me is to use Http.send and carry Result within your message and then use RemoteData.fromResult in your update function to turn it into RemoteData.

ah, that makes sense. I was also thinking that it's a weird library that forces you to handle NotAsked in your update function after you've told the library to go and fetch a resource. How on earth is it possible that it comes back to you with NotAsked? Using it only to store it in your model and use Http.send instead makes sense.

gyzerok commented 6 years ago

Closing since there seem to be no interest to moving further with this