> Elm has an incredibly powerful type system
Near the end of the article:
>Want to decode some JSON? Hard, especially if the JSON is heavily nested and it must be decoded to custom types defined in your application.
IMHO the lack of typeclasses/traits is really hurting Elm. Take haskell f.e.
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
data Person = Person {
name :: Text
, age :: Int
} deriving (Generic, Show)
instance ToJSON Person
instance FromJSON Person
While I understand Evan's aversion against complexity, it makes me a bit wary about using ElmLang in production. I am currently using TypeScript, but if I would need a more powerful type system, I would probably switch to Haskell/PureScript or OCaml/BuckleScript instead.Writing decoders in Elm is not hard. It's manual. It's explicit. It forces you to specify what should happen if the JSON object has missing fields, incorrect types, or is otherwise malformed. There's a slight learning curve and it can be time consuming at first, but it guarantees that your application won't blow up at runtime because of some bad data. Because of this, JSON decoding is frankly one of my favorite parts about Elm.
Typescript, on the other hand, offers no such guarantee. If you write a function that takes an Int and you accidentally pass it a String from a JSON response, your app will blow up and there's the nothing the compiler can do to help you. Personally, I'd rather write JSON decoders than have my app blow up because of a silly mistake.
But to claim that JSON decoding in Elm is not significantly more difficult than it is in JavaScript would be misleading. A front end developer that has only written JS will be surprised when he/she cannot just drop in the equivalent of JSON.parse() and get an Elm value out of it. I call it "hard" because there is a bit of a learning curve, and it does require some thought, and quite frankly it takes quite a bit of time if you have a large application like we do.
Moreover, I am not complaining. And I do not think people should be. As I said in the article, the tradeoff is worth it.
For example, here's how you rename a JSON field while serializing/deserializing a data type in Rust:
https://play.rust-lang.org/?gist=1b382bc1572858841d5e392435d...
You just annotate the field with #[serde(rename = "..")]. Here is a list of such annotations
https://serde.rs/field-attrs.html
Serde is also generic in the serialization format; the example I linked uses serde_json, and was adapted from its README here https://github.com/serde-rs/json
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.
data Person = Person
{ personFirstName :: Text
, personLastName :: Text
} deriving (Generic)
instance ToJSON Person where
toJSON = genericToJSON $ aesonPrefix snakeCase
instance FromJSON Person where
parseJSON = genericParseJSON $ aesonPrefix snakeCase
Which produces messages like: {
"first_name": "John",
"last_name": "Doe"
}I've worked on various ways to do JSON encoding in Purescript through datatype generics, but the recent RowToList machinery lets us use record types directly. I have a post and some links collected if anyone is interested:
https://www.reddit.com/r/purescript/comments/6mss5o/new_in_p...
You can go through the tutorial and know the language in two days. Seriously if you ever want a weekend project, try learning elm.
Even if you don't want to use it, but just to be exposed to the ideas.
You csn be writing productive code in a week. If anyone tell you Haskell (or Purescript) is less than months to be productive is not being honest about what production level productivity entails. You can learn the fundamentals in less time, but the prized significantly more complex abstractions makes ramping up significantly slower. And this is coming from someone who likes haskell and is enjoying learning the language.
As someone who has used a lot of languages over the years, I really do say spend a weekend trying it out. The language has great ideas and a great philosophy.
But perhaps it wouldn't save much code compared to custom-made parsers.
The amount of work you need to do in Elm instead is huge.
When you only have thousands of items in an array, you end up with performance discrepancies between older browsers like IE 11 and Chrome that are unacceptable outside of a toy application.
Can you explain what you mean here? Your explanation below suggests a runtime cost for the type system, which just isn't true.
I'm aware Argonaut's approach generates slower code. The nice part of any such happenstance in PureScript is that if you do find an issue it's easy to bang out some Javascript in a foreign call to do what you need.
I find outside of Slamdata, folks are keen to do this. PureScript is a very new language and is still finding ways to generate code efficiently in it's target environment.
In every single aspect. I just wish that it will get mainstream as soon as possible.
His article is spot on, and agrees with what I've seen, and most others that used Elm. Just look it up.
For an average JS developer Elm is totally alien tech compare to React or Angular
To turn your argument the other way.
The JS landscape, where "trendy" libraries change every few months, is also alien to anyone that doesn't keep with the latest libraries every few months. Is that not worse?
I don't have to explain "JS fatigue", it's a fact.For example, I just got into a new team, and I have to now use what they use. I've been doing JS for 4+ years, and that stack I'm facing now, is alien and confusing to me. I have to invest time and learn what the hell those redux-sagas are, or what "magic" create-react-app tools hide, and in general, it's very confusing so far.
With Elm, I spent a few weeks 1 year ago to learn the language, and that was it. All the libraries are very simple to use because of the language. I doubt that learning "redux-sagas" will help me a year from now.
Also, Elm, gives a very coherent package. You don't have to transpile or add a linter, or a type checker, or stitch new libraries every few months because the trend changed. No webpack or babel or eslint or immutable.js or typescript or flow or any of those.
Not to go into the part of the safety and confidence the compiler gives you. Typescript or flow are not even close to that quality and guarantee level. Note that again you have to learn their syntax and configure them, so that's again a cost on top of learning JS. And they are as much alien as Elm.
FWIW, I start now side-projects in Elm, and I get things done way faster than with JS. And that's also what I see from other people as well, regardless of whether they come from JS or another language.
>In every single aspect. I just wish that it will get mainstream as soon as possible.
I completely agree - I will be a huge benefit to the industry when elm is as common as js. The concepts it introduces, the benefits it shows are all huge.
I've been programming a long time, and elm shattered my views of the relationship between a coder and his/her compiler. It went from being that thing you have to "pass" to see your code work, to instead a faithful companion. Like having a trusted dog with you in the woods - an extra set of senses to help you out on your way.
Elm is not perfect (no language is) -but being perfect isn't the goal. A practical transition story, huge productivity gains and extremely maintainable code.
I love Elm as much as the next guy, but be aware that you can get many, if not most, of these same benefits while working in JS if you use Flow.js or Typescript. I prefer Flow because the community is more oriented toward functional programming vs OOP in Typescript, but they're both capable static analyzers for JS.
And sure, when more people join Elm, bad-written libraries or silly paradigms will crop up, but I'm confident it won't be as bad as the JS landscape is today.
Time will tell I guess. -- Now back to stitching those JS libraries to write some code that might work. Bills have to be paid :)
Here are some JSON tools for Elm:
- json-to-elm http://json2elm.com
- swagger-elm https://github.com/ahultgren/swagger-elm/
- elm-graphql https://github.com/jahewson/elm-graphql, https://github.com/jamesmacaulay/elm-graphql
- elm-export https://hackage.haskell.org/package/elm-export
Because this [1], even though it seems to be just a experiment so far, looks really good.
I.e: doing
type MyTestStrMap = { a :: Int , b :: StrMap Int }
and then just calling
let result = handleJSON """ { "a": 1, "b": {"asdf": 1, "c": 2} } """
let newResult = doSomething (result:: Either MultipleErrors MyTestStrMap))
is kinda all I ever wanted in these haskell inspired languages?
[1] https://github.com/justinwoo/purescript-simple-json/blob/mas...
Was wondering whether it might be a slow server, but the app.js for the TodoMVC appears to be over a megabyte (1.21 MB, have a 4000 line Mithril app which is 500k uncompressed). What's up with that?
>> https://github.com/dmjio/miso/blob/master/examples/xhr/Main....
There’s no reason to use genericParseJSON here, you can just write “instance FromJSON APIInfo”. Or leave the instance declaration as it is and safely rename your record fields to e.g. currentUserUrl instead of current_user_url.
The purpose of the camelTo function is to convert “camelCaseExample” into e.g. “camel_case_example” by passing ‘_’ as the first argument. But the APIInfo data structure already uses record fields with underscores in them.
[1] https://fable-elmish.github.io/elmish/
[2] http://fable.io/
Now maybe Elm should stay simple and not jump all the way to multi-parameter type classes with functional dependencies, but it is already forced to pay some of the complexity burden to decide which types admit equality testing, comparison and so on.
Then again, I'm also one of those wierdos who think Go works just fine despite the lack of generics (though I think Go needs generics more than Elm needs something interface'y). Still, things are getting done and I'm happier than with most other languages.
You have to do that in Haskell as well (where »you« can be you or the compiler via DeriveFunctor); the key difference is at the use site: there is one map function (called fmap) that does the mapping, whereas in Elm (and pretty much all other languages that don’t have higher kinds while we’re at it) you have to use the specific map function for that type.
Erlang is FP. Javascript is FP. Ocaml is FP.
Type classes, or anything else that has "types" in them such as dependent, or liquid, are not FP, they are types. Types that found their way into a couple of FP languages.
What all these things have in common is: it's easier to reason about the values in a program than the program counter, and that destroying information makes it harder too. And that, to a great extent, is why FP is useful. Even really basic FP in C# or Java.
and then
> Want to decode some JSON? Hard, especially if the JSON is heavily nested and it must be decoded to custom types defined in your application. Doing this will require an understanding of how JSON decoding works in Elm and will usually result in quite a bit of code (our application contains over 900 lines of JSON decoders alone).
What a great pragmatic language
JSON decoding is hard relative to what it is like in JavaScript. In your JS code you can just call JSON.parse() and get the corresponding JavaScript object.
In Elm, decoding is not nearly as easy as it is in JS because every field must be explicitly converted to an Elm value. Depending on the complexity of your conversion from JSON to Elm value (e.g. whether you are just decoding to primitive values or to custom types defined in your program), there may also be a bit of a learning curve.
As I stated in the post, there is a benefit in doing all of this: your Elm application will effectively type-check your JSON and reject it if it is malformed.
And you don't have to write that bit by hand:
http://eeue56.github.io/json-to-elm/
will generate the decoder for you for straightforward cases and give you a great starting point for more complex cases.
You also get proper ADTs to manage state: not asked/loading/loaded/error something that is tedious and error prone in Javascript.
An online converter is definitely not something I would advertise for a "production-ready highly productive amazing pragmatic language to be used in a serious company"
instance FromJSON Foo
and that's it.Programming in Elm had been a delight, especially when you let go of OOP and embrace functional concepts and practices. On one hand, you lose mental tools that you've relied on, but you gain the other tools you didn't even know existed before.
Where I really disliked about Elm is when I had to encode or decode JSON. It's a giant royal pain in the ass. Also, when you find you have to break out to JS often for libraries you don't want to write yourself, it's not a good fit--as I found out when write a toy interactive notebook to render markdown in Elm.
But for most SPA that just manipulate form data and communicate with the server, it's a pretty great fit.
- json-to-elm http://json2elm.com
- swagger-elm https://github.com/ahultgren/swagger-elm/
- elm-graphql https://github.com/jahewson/elm-graphql, https://github.com/jamesmacaulay/elm-graphql
- elm-export https://hackage.haskell.org/package/elm-export
Once you've got the hang of it, it's not that hard either. Just time consuming. That's where json2elm comes in :)
Also, once you get the hang of it, you can use it as your safety layer for incoming data. To guard the app from any invalid response.
I've even used GraphQL for one of my projects, decoding worked fine as well.
So what you're frustrated with is that a group of likeminded individuals celebrates their common point of interest and doesn't make room for you to nay-say them?
> So much of Elm community dialogue (in talks, in articles, in the Elm slack which I follow daily) is simply those with more experience initiating those with lesser experience into the "Elm way" of doing things
This could be said of a lot of PL environments. How many Python tutorials stop midstride to browbeat you about how great Python's way of doing things is? Sure feels like a lot to me.
> The inability to even acknowledge the unprecedented labor required simply to parse a JSON response is a perfect example of the cultish mentality emerging in this community.
Right but... what do you want? Acknowledgement? My friend, literally everyone here is agreeing it's harder than JSON.parse. No one argues it is "easier." I can't find a handy example in this thread of anyone saying, "Yeah this is great." There are tools to ease this pain, and there are ways to call external JS code that does this.
At the end of the day though, validating data structures on the wire both structurally and for content is a lot of work. Most Javascript projects don't do it. Hell, most typescript projects just say, 'Well if it breaks it breaks.'.
By pure coincidence this redacted typescript snippet is up on my other screen:
function validateTaskArgs(cdata: any, ldata: any): [boolean, IDomainObject1, IDomainObject2] {
if (cdata === null || ldata === null) {
return [false, null, null]
}
const cdkeys = ["key1", "key2", "key3"]
const ldkeys = ["key1", "key2", "key3", "key4"]
const checkKeys = (keyset: string[], obj: any) => {
const result = keyset.reduce(
(p, c) => { return p && !!obj[c]},
true)
return result
}
return [
checkKeys(cdkeys, cdata) && checkKeys(ldkeys, ldata),
cdata as IDomainObject1,
ldata as IDomainObject2
]
}
This ugly bit of custom logic just to validate a pair of objects in a larger json datastructure, and even that has bugs. This can't deal with a bunch of problems, but it's just too much pain to actually string together the logic in a composable way in typescript, so I accept this kind of drudgery.But earlier I actually linked a much more sophisticated piece of purescript that's about the same size and not only is easier to read and is self-describing (the code to build the objects IS the spec), but it uses those properties to report console errors. You can see it here:
https://gist.github.com/KirinDave/9af0fc90d005164743198692f3...
If you want to make Elm better at JSON, that's the sort of stuff you wanna ask for. And folks will rightly be resistant. Because the programming concepts that make that work (free applicative, in this case) are not things the elm target audience has learned about, yet. Elm's leadership is acutely aware of how big a stack of new concepts they're putting on everyone's plate, and they're cautious about offering more.
> So what you're frustrated with is that a group of likeminded individuals celebrates their common point of interest and doesn't make room for you to nay-say them?
I'm not frustrated with responses to criticisms I've made. In fact, I haven't made any criticisms (except of course, the ones in the last comment :P). So I don't have any experience of anyone not making room for me. But I have observed some smart people with well-articulated suggestions get shut down. It's not that their suggestions weren't accepted but it was the way that their ideas were received. I haven't actually seen someone who isn't Evan C. contribute something significant that isn't "doing X like Evan would do it." In the entire world of this language, there seems to be 1 architect and a community of implementers. Now, there's nothing wrong with being an implementer. I am an implementer. But it seems easy to see that cultures are healthier when there are a diversity of ideas.
I think that your characterization of the Elm community as a "group of likeminded individuals celebrat(ing) their common point of interest" is actually close to what I'm talking about. It's great when a programming language community is passionate about the language. If people enjoy using that language, it's certainly a good sign. But I wouldn't trust the judgment of a group of people who can't critique what they love and are unwelcoming to those who do.
The counterpoint here is Dan Abramov and the Redux community. Dan is continually pushing people to understand why they are using Redux and not to see it as a solution for everything. That kind of transparency, and the continual acknowledgement by Redux maintainers that there is more than one good way to do something, is the kind of intellectual honesty that I'm using as a standard in my assessment of Elm.
> How many Python tutorials stop midstride to browbeat you about how great Python's way of doing things is?
I couldn't say and it wouldn't change my opinion of Elm.
Clojurescript's 're-frame' lib implements something similar to the Elm architecture and is quite pleasant to work with.
How does the Elm experience compare to the Clojurescript experience ?
> Just start by changing your type definition and the compiler errors will help you find everywhere that needs updating.
This is how most of Elm development gets done. I change the types and let the compiler tell me what actual code needs changing.
Clojurescript, on the other hand, doesn't have this "assistant", unless you're using Typed Clojure or Schema (it's been a year or two since I tried that, so can't speak from current experience).
The Elm Architecture is worth pursuing even in dynamically typed languages though :)
1) Re-frame and Elm are both backed by a virtual dom (Re-frame leverages React behind the scenes, Elm uses virtual-dom)
2) Both impose a single app state
3) Both require user events and outgoing state mutation to be explicitly defined somewhere outside of the context of a view
4) Both operate on a sort of "game loop" style of processing events and calling view functions
5) All data is immutable in both languages (though Clojurescript provides explicit mutable structures if desired, which require special syntax so any mutation is evident)
Differences:
1) Elm's model is pure FP -- data is passed to a function, and then it calls other functions. An individual view function in Elm cannot independently subscribe to any kind of data event, either a state change or a socket message, etc; it must receive the data it needs as an argument to the function only. Depending on who you talk to, this is either limiting or properly restrictive. It does mean that a branch of your Elm user interface should generally correspond to a single branch of your app state, or that view functions in complex interfaces must take quite a lot of arguments; this is a pattern for which re-frame explicitly offers a workaround (though you can also do it the Elm way in re-frame if you want). In general, these differences lead to more re-usable components in re-frame than in Elm.
2) Elm has no real asynchronous library for high-level concurrency. Most Clojurescript projects (at least large SPAs) often leverage core.async as an abstraction for large UIs that handle many independent processes simultaneously. Core.async makes clojurescript particularly versatile for accomplishing things in a single-threaded environment as if it were modelled with multiple threads.
3) Elm has its own compilation story that is separate from the JS ecosystem. The Elm devs are working on dead code elimination. Clojurescript projects are all built on Closure, which provides DCE, compression, and global inlining which can provide some projects notable speedups. The Closure libraries also give Clojurescript cross-browser abstractions for hundreds of common tasks in front-end development that have been battle-tested by Google in all of their web services, so it can greatly reduce development and debugging time for complex apps that run everywhere.
4) Elm controls JS interop much differently than Clojurescript. The tradeoff is that in Clojurescript you will inevitably spend more time debugging runtime errors, while in Elm you will spend more time developing your JS interop code and testing cross-browser compatibility.
5) There are many UI libraries available for re-frame because it can wrap existing JS tools (including wrapping UI libraries built for React). There are fewer for Elm, and it's more common to roll your own UI in Elm. There are pros and cons here. I've worked on two large projects in Clojurescript, one which was all custom UI components and another that leveraged polished libraries in the wild. The former took much much longer to make but looked more unique. Having the choice to go either way is a big benefit since project goals widely vary. If you are primarily a developer/coder and not a web/graphic designer, you may be frustrated at UI design in Elm. If you work with a designer, this is not a problem. But if you work alone, having access to a lot of UI tools can free up your time and efforts quite a bit, and Clojurescript has an edge here.
6) Types: Elm has static types, while Clojurescript offers clojure.spec (which is optional though widely used). Spec is a runtime contract system so you can guarantee that all args passed to functions or setup in data structures meet specified criteria; not just types of the args, but also any other predicates, such as a valid range for an integer, etc. For example, an Elm union type would be a Clojure set, where each element could be a specific data structure with other specs attached to it. However, Elm has proper static types, which are caught at compile time, not runtime. There are benefits of both styles. If you want to tightly catch very specific data aberrations that flow through an app, clojure.spec is easy to use for that purpose; but if you want more general checks before the program runs, Elm would be preferable.
7) Performance: All of Clojurescript libraries that wrap React (including Re-frame, vanilla Reagent, and Om) offer an interesting runtime optimization that I've not seen in other languages, and is not available in React itself. If the data that a view function requires (either via its arguments or subscription) has not changed from the prior render frame, then the entire view function is skipped without running, since its output would not be any different. What this means is vast sections of your app's code don't even need to run on each render frame, and this is not trivial. In a small app, it would make no difference, but in complex SPAs this can be very significant. Elm offers a library, Html.Lazy, that attempts something similar, though my impression is that most Elm projects do not use it since it can be tricky to explicitly add this behavior; in Clojurescript, it is built in automatically. In Re-frame, they go an extra step by de-duplicating any queries or subscriptions so that multiple views which use the same data context do not query, fetch or calculate the required data more than once, which is then passed to all requested functions. If you are working on an app that processes lots of data frequently, this can be the single selling point of using Clojurescript, as it frees up the CPU to deal with only those things that are guaranteed to require processing.
Clojure's syntax is very tight and concise, while Elm's language is elegant but more verbose. I have found that there is approximately a 2.5X increase in code size in Elm when trying the same ideas out in both languages.
All other things being equal, you can probably get a re-frame app going very quickly; if you need to prototype something or get to an MVP as soon as possible, it is really hard to beat Clojurescript compared to any other compile-to-JS language. If however you are going for application purity and a reduction in runtime debugging, Elm (or Purescript or some other statically-typed languages) would be a better fit.
Ultimately, Clojurescript is a general purpose language, while Elm has a very specific use-case; if you fit into the Elm model, it can be nice. If you need to reach outside that model, it is a challenge.
Large projects are absolutely doable but require more discipline and experience with the language from developer. For example you can start with a crude prototype and introduce clojure.spec later. When you do this is entirely your choice while in Elm you just have to write function signatures and types/structures definitions from the beginning.
ClojureScript has a very easy interop with JavaScript. Actually this is one of design goals. Consuming JavaScript library is super easy. For example most of virtual DOM libraries are built upon React. This is good because writing performant virtual DOM with older browsers support isn't an easy task.
The toolchain has code splitting and dead code elimination. It had it long before Webpack and can even optimize imported JS libraries code. As far as I know Elm still can't do it.
Another good thing is you can use Clojure(Script) both on the client and the server. This allows you to nearly skip data serialization/deserialization using powerful "transit" library. Actually you can use transit on server with several other languages. So it's not a lock in but it's much smoother with Clojure.
The libraries ecosystem is rich. For example Clojure(Script) has a mature WebSockets library (it's both server-side and front-end) with any kind of fallback you can imagine.
More freedom on type-level programming would help, but that would certainly complicate the type system, and the only language I know that lets you fold over arbitrary record types is Ur/Web, which has a richer type system than even Haskell, and I don't see Elm adding things to its type system (other than type classes, hopefully), given that other interesting features were already removed because very few people used them.
> Want to measure the height of an element on the page at the moment a user clicks on it? Hard, in order to do this we had to make heavy use of event bubbling and writing JSON decoders for the native event.target object that is produced by an onclick event.
What would be considered best practice?
Is it best practice to re-implement this sort of thing? Seems one could easily find vanilla JS code and encapsulate it, or add a helper lib?
In Elm, decoding is not nearly as easy as it is in JS because every field must be explicitly converted to an Elm value. Depending on the complexity of your conversion from JSON to Elm value (e.g. whether you are just decoding to primitive values or to custom types defined in your program), there may also be a bit of a learning curve.
As I stated in the post, there is a benefit in doing all of this: your Elm application will effectively type-check your JSON and reject it if it is malformed.
I'm not making a joke but this is a valid point. And if it had ELMON (Elm object notation), things would be more straight forward.
- json-to-elm http://json2elm.com - swagger-elm https://github.com/ahultgren/swagger-elm/ - elm-graphql https://github.com/jahewson/elm-graphql, https://github.com/jamesmacaulay/elm-graphql - elm-export https://hackage.haskell.org/package/elm-export
It's worth noting that JSON decoding is not _hard_ as such, but it's harder than other parts of the language. It is more time consuming. Tools like these can help reduce the amount of time you spend hand writing decoders.