I’m working my way through Elm in Action and this code works on my local device, but not in the Ellie App — I know the error is being reported on line 167:
GotPhotos (Err _) ->
( { model | status = Errored "Server error!" }, Cmd.none )
Navigating to the link "http://elm-in-action.com/photos/list" downloads a file, rather than allowing you to view it in the browser (at least on Safari) which might be where the problem lies.
I have no idea how to fix it. I’ve tried Debug.log in various places but I don’t really know what I’m doing! When a program gets more involved like this one, I’m finding it a bit difficult to think about the flow of the program.
Questions
When using an external server what are the steps to debug?
Best steps to find the root of the bug?
How can I better use Debug.log or other tools to aid discovery?
How can I better “see” the flow of the program?
What tutorials will help me better understand this program’s flow?
In the past I’ve broken up the program into smaller chunks, to view things in isolation, which helps to understand things, but doesn’t help much if the server’s not returning things properly.
I’m also struggling to wrap my head around Status which is being cased in two places (view and update) and understanding Result.
As far as I understand it, GotPhotos is the main function that sets the model.status (as one of the Status type variants) but Result is returning an Err so it’s probably a server file issue. What I don’t understand is why it’s working on my laptop and not in the example above.
As a program becomes more complex (especially the next part, building a JSON decode, which seems quite difficult) it’s important to be able to visualise the moving parts, but I’m finding it a little difficult to fit all the moving parts in my head.
What will help me visualise this better, and solve bugs if they occur?
Whenever there’s making requests to a server involved, I think it’s best to forget about Elm for a little bit and remember that Elm is just a way of doing web stuff in a nicer language. You can open the browser’s dev tools, and switch to the Console tab. I’m using Firefox here, but it’s similar in all browsers. Firefox prints the following error in the Console: Blocked loading mixed active content “http://elm-in-action.com/photos/list”. It means that the browser did not allow loading HTTP content on an HTTPS site (Ellie uses https:// as you can see in the address bar).
When I got started with Elm, I already knew HTML, CSS and JavaScript and how browsers work. I can imagine it must be difficult if someone is new both to web development and to Elm at the same time! Especially knowing what are “Elm problems” and what are “that’s just how browsers work problems.”
Funnily enough I’m pretty experienced with HTML/CSS and have used a fair few frameworks like Wordpress and Craft, mostly dealing with same-site data like post for forms etc. I haven’t dealt much with JSON APIs.
So yeah, I wouldn’t have thought to look for that bug; I don’t use the Console much. I’ll look there next time — the Result error in the debug tab wasn’t very helpful.
Firefox’s console error message also links to this article about mixing HTTP and HTTPS, it should explain why browsers forbid that:
Whenever something didn’t work as expected, I start with opening the devtools, just in case I find something interesting in the console logs, or in the Network tab, or to inspect the page (“maybe my element did render, it’s just that I can’t see it?”). Sometimes I keep it open all the time just in case (but sometimes it takes too much space). Only after that I start to dig into my Elm program.
In your case, if we pretend that I didn’t see anything in the console, I would debug like this:
I notice that it says “Server error!” on the page. I search for “Server error” in the code and end up in this branch of the update function:
GotPhotos (Err _) ->
( { model | status = Errored "Server error!" }, Cmd.none )
This tells me that when the app gets a GotPhotos message with an Err inside, we set the status to Errored "Server error!", and that is the only place we do that.
If we check the view function, it does indeed render that error message:
Errored errorMessage ->
[ text ("Error: " ++ errorMessage) ]
It’s good to validate your assumptions, so you could:
Stick a Debug.log in the GotPhotos branch of the update function, to verify that we actually get there. Maybe like this:
GotPhotos (Err _) ->
let
_ = Debug.log "We actually get here!" () -- the () is important, don’t forget it
in
( { model | status = Errored "Server error!" }, Cmd.none )
This code uses a good trick: When you want to Debug.log something that isn’t part of an expression nearby, you can use an ignored let to print, a bit like you might do in a language with side effect statements. In this case, we’re only interested in the log message, but we still have to supply a value to log, so we use () (called “unit”).
Temporarily change the "Server error!" text to something else, to verify that this string is actually connected to what is displayed on the page.
Then I’d start following the GotPhotos message around the code. Who triggers it? In this case, it’s mentioned only four times: In the Msg definition, twice in the update function, and once in initialCmd. The definition just defines that it exists, so it’s not interesting. The update function only pattern matches on it, so it doesn’t say anything about when it is triggered. That leaves only one place: initialCmd. And it does actually use GotPhotos as a value, which means it can be triggered. It hands GotPhotos to Http.get, which comes from elm/http, which has the ability to trigger messages. There is a finite number of core functions that can trigger messages, and after a while you learn recognize them all.
Then it’s time to follow the initialCmd value. When is it used? Turns out, only once, in the init of main. Reading about The Elm Architecture, you’ll learn that init is used on startup, and has the ability to return Cmds, which the Elm runtime then executes.
We have now learned that the program is set up to make an HTTP request at startup, and it fails. We have verified with a Debug.log that at least one assumption about how the program executes is true. What can we do next?
We want to know why the request fails. Can we get more details? Turns out we can! We have more details, it’s just that the program is ignoring them with an _ wildcard in the pattern matching:
GotPhotos (Err _) ->
( { model | status = Errored "Server error!" }, Cmd.none )
I think it’s good to display more details on screen, so it’s easier to understand what went wrong. I guess the example tries to be short and simple, but that’s good practice in “real applications”.
Let’s log that and see what it says:
GotPhotos (Err error) ->
let
_ = Debug.log "Error" error
in
( { model | status = Errored "Server error!" }, Cmd.none )
It prints: NetworkError. Which isn’t a whole lot of detail, but at least something. The docs describe it like so: “NetworkError means the user turned off their wifi, went in a cave, etc.”
At this point we might start to suspect that the Elm code is correct, and that the problem is with the server or how browsers deal with requests.
Thanks for the detailed feedback, I’ll have to distill those pointers down and get used to slightly larger programs. I’m starting to struggle to grasp concepts a bit — I think finding other examples/tutorials helps.
I found another great example of Http.get with a simple string at Beginning Elm. I think it helps having each functionality isolated so you can properly see what’s going on.
That tutorial also has a problem with cors — even though you’re serving both servers on localhost — but thankfully you can change the heading returned by the server with --cors flag to allow it.
So NetworkError could be anything then! I like the fact that you can keep the Debug.log separate in a let. Nice.
I’m sure I’ll learn more about error-checking and security either in the book, or further research.
I have used that method of Debug.log to demonstrate how the primes are sieved from the integers in range 1 to 100 in different stages of the program. The output of Debug.log can be seen either in Ellie tab LOGS or in your own local browser console. The output of main consists only of the final list of primes.
The Debug.log is located in the recursive function lines 20 - 45 of the source.