At some point, after enough people tell you that they can't figure out how to do something, one should admit that it is difficult.
At some point, after enough people tell you that they can't figure out how to do something, one should admit that it is difficult.
json.loads('foo.json')
I spent 3 days about 6 months ago, off and on, trying to figure out json decoding in elm, and gave up on the language entirely. It wasn't a matter of simply specifying types. The syntax is confusing the compiler isn't any help. I had very little difficulty working with anything else in Elm, so I think there is a lot of work to be done there.
If there were a more painless way to do it, there are a lot of api's I'd love to write front ends for in elm.
Generate JavaScript with great performance and no runtime exceptions.
Type safety doesn't come for free. If you want to build an application that doesn't have runtime exceptions, you have to specify what external data should look like, how to translate that data into a native datatype, and what to do if that expectation fails. With this in mind, you can't just write `JSON.parse(data)` and expect everything to work. What if your data has a different shape? What if it's missing fields? What if field "foo" is a String instead of an Int?Here's a short example in Typescript:
import fetch from "node-fetch"
interface Post {
user_id: number,
id: number,
title: string,
body: string
}
const postUrl = 'https://jsonplaceholder.typicode.com/posts/1'
const show = function(post: Post) {
console.log(post.user_id)
console.log(post.id)
console.log(post.title)
console.log(post.body)
}
function getJSON(): Promise<Post> {
return fetch(postUrl).then(x => x.json())
}
getJSON().then(show)
Do you see the bug? `user_id` is supposed to be `userId`, yet this example will compile without an issue. When you run it, `post.user_id` will be undefined, even though our interface clearly states that a Post most have a `user_id` field of type `number`. The compiler won't catch this even with `--strictNullChecks` enabled.Everything in programming is about tradeoffs. If you don't care about strong type correctness, use something like Typescript. If you do care, Elm is a great choice.
[1]: http://elm-lang.org/
The only difference in your example probably is that Elm will force you to handle the case where your interface doesn't match the response object (missing field, in this case). But nothing in Elm forces you to handle it well, so if the programmer forgot to handle it well in TypeScript, they will do the same in Elm, and the end result will be the exact same: a runtime failure.
No.
> The only difference in your example probably is that Elm will force you to handle the case where your interface doesn't match the response object
Which means that no exception have been thrown. You've handled it, however poorly, and so you've made a conscious decision about how to deal with it.
In TypeScript, this is much easier to forget, because you don't have a compiler that forces you to deal with it. If you're lucky, this causes a runtime exception at the place the error occurs. More likely, however, no exception is raised, and you get an error down the line which is hard to notice and hard to debug.
Like, there's nothing in TypeScript that forces the developer to use the correct types. A lazy developer could just use "any" everywhere. So if the programmer doesn't handle this well in JavaScript, they will do the same in TypeScript, and the end result will be the exact same: a runtime failure. This, however, doesn't mean that TypeScript isn't a great tool that helps developers avoid runtime errors, it just means that this particular programmer doesn't take advantage of the tools at his/her disposal.
What I have a problem with is using an example where the programmer is lazy in TypeScript, then assuming the programmer is NOT lazy when coding in Elm.
If you just print an empty error string and your app continues running assuming it decoded successfully, you can be in as bad a place as the app that crashed. Actually, in some mission critical cases, failing hard is safer than continuing with corrupt data.
In both cases, TypeScript AND Elm, you must do work to handle the failure to decode gracefully, it will actually be less work in TypeScript because you have less to specify. The only difference is that in Elm, you are reminded more often to do that work.
To catch this bug in TypeScript, you'd have to write runtime validation, which you are less likely to do, since you can just do JSON.parse. Elm, again, forces you to write runtime type validation, and handle the potential error (which could be something simple as crashing the entire application).
When it comes to reliability, that makes all the difference.
And I'm not arguing that Elm is great because of poor developers either. I do theese sort of things when I write typescript, simply because I just don't think about the error case that often. Elm is a wonderful straight jacket that makes me a better developer <3
But it does not force you to handle failures correctly (as your lazy programmer example proves). Forcing you to handle a Left value from an Either type is not the same as forcing you to handle a Left value correctly.
Elm doesn't save you from bad failure handling, which is what the post I replied to claimed.
What Elm does do is remind you that you should handle failures, which I think is very valuable and I'm not arguing against (and is what you're arguing for). But you still need to write a correct handler, and Elm won't force you to do that!
This is completely false. Elm's type system will force you to handle the decoding failure at compile time. The `decodeString` function has the following type:
decodeString : Decoder a -> String -> Result String a
Which means that when you call `decodeString`, it will return a Result containing either the decoded value or a String describing the error.This is not a "runtime failure". This is the compiler forcing you to explicitly spell out what you want to happen the case of a decoding problem. The difference between Typescript and Elm is that Typescript's compiler will happily let your program crash when this type of issue occurs, whereas Elm's compiler won't.
So, no, what I said is not "completely false"
You have one case branch that looks like:
GotUserList (Ok userList) -> ...
and another that looks like: GotUserList (Err _) -> ...
Since we need to pattern match on both types of the Result structure in our update cycle, there isn't really an opportunity to implicitly gloss over a failure to parse. Of course you can try to make the decoder super permissive, but this is harder than doing it the right way. If you're REALLY lazy, you can throw in "Debug.crash" in the failure case, but there's no general solution for throwing grenades at your foot :DAnd yes, it is. Here is code from my own project:
Json.map3 HintTemplate
(Json.field "ID" Json.int)
(Json.field "Text" Json.string)
(Json.field "Image" Json.string)
Where HintTemplate is a record type containing an integer id, a string text and a image url. What about this is difficult? instance FromJson HintTemplate where
parseJson = withObject $ \o ->
HintTemplate
<$> o .: "ID"
<*> o .: "Text"
<*> o .: "Image"
I wrote <$> and <> out but there is a definition like map3: liftA3 f a b = f <$> a <*> b
This works for all applicatives, though. Is it normal for elm libraries to copy paste code like the map3 definition?