Elm by Example
elm-by-example.org
elm-by-example.org
1. Elm encourages highly-rigorous state handling, thanks to the React-like "Elm Architecture" https://github.com/evancz/elm-architecture-tutorial/ and features like the Maybe type and ADTs. I rarely worry that I've overlooked some horrible, asynchronous corner case or that something might be null.
2. The Elm architecture requires a considerable amount of boilerplate code, at least relative to something like Ember.js. It's often necessary to thread significant amounts of application state through parameters, for reasons similar to the ones here: https://docs.google.com/presentation/d/1afMLTCpRxhJpurQ97VBH... But when you combine that with the need to use "case" matches everywhere to destructure ADTs, and the fact that you can't define guard expressions (or destructure things nested inside record types), it tends to result in a lot of extra typing.
3. The Elm tooling is a mixed bag. The error message are the best I've ever seen, but the sweet time-travelling debugger just refused to run when I tried to use it with the Elm architecture tutorials. This pattern of brilliance and brokenness feels fairly pervasive at this point.
4. The Elm community can occasionally be a bit frustrating to work with as an outsider. Sometimes, when I run into an issue, I eventually find a GitHub discussion like this: https://github.com/evancz/start-app/pull/37 , where somebody is trying to address an actual issue (one that was biting me, too), and the community is more-or-less throwing up its hands. Also, the Elm community has an occasionally surprising tendency to close GitHub issues quickly, even if it's known that they'll probably need to fix something someday: https://github.com/evancz/start-app/issues/35#issuecomment-1...
5. For me, Elm feels considerably more restrictive than programming in Haskell.
6. Elm's type checker and design do have one huge payoff: Even very complicated apps with lots of tricky asynchronous state tend to work on the first run after they make it past the type checker. This is hugely valuable, and it makes me more inclined to forgive Elm's limitations elsewhere.
Overall, Elm is exploring some really important ideas, and it's pushing the general React-style architecture to its logical limits. I think I'd consider it for small- to medium-sized apps with lots of tricky state, and with developers who are strong at functional programming.
Until I found PureScript. Especially purescript-halogen [0], which is basically how one might make React in Haskell. Everything is still pure, but you can define nested state (not to be confused with React-style local mutable state!) without Elm's boilerplate wiring.
The "update" function runs in the continuation monad, which means that one can do something like this:
modify (\(State s) -> State $ s { loadingUsers = true })
users <- liftAff' $ fetchUsers
modify (\(State s) -> State $ s { users = users, loadingUsers = false })
and this runs asynchronously, but it still modifies one global state. Compare with Elm's "return fetch effect, fire event, react on event in update function".There's everything one might need in PureScript: functors, applicatives, monads, so the language feels much less constraining than Elm.
Drawbacks:
- they use bower for package management - it's ok, but elm-package's enforced semversioning is really nice.
- no default imports, so imports can get verbose [1]
- the error messages might not be as nice as Elm's, but I haven't had a problem so far, at all
- no elm-reactor - though since halogen is just a library, the UI is pure and there's no magic state-isolating runtime, you can probably roll your own relatively easily
- not as easy to pick up as Elm - but no problem for a Haskell programmer. Elm definitely focuses more on user-friendliness and Javascript converts.
...but these are minor nitpicks - I am in the process of making the switch and porting an app from Elm right now, and it definitely was the right decision.
[0] https://github.com/slamdata/purescript-halogen
[1] https://github.com/slamdata/purescript-halogen/blob/master/s...
I will say that the types in Halogen seem more complex, but they can be justified by the fact that the coproduct machinery helps ensure all actions get handled somewhere. This may be possible with prisms, I'm not sure yet. This library [3] seems relevant.
One issue I've found, which I understand the Halogen folks are also finding, is that it is tricky to wire up action handlers such that a subcomponent can invoke an action and have a parent act on it and invoke its own action in response. Granted, this is a relatively infrequent use case, and you can always push such logic into some shared parent.
[1]: https://github.com/paf31/purescript-thermite/ [2]: https://github.com/zrho/purescript-optic-ui [3]: https://github.com/joneshf/purescript-totally
[1] http://package.elm-lang.org/packages/evancz/focus/1.0.1/Focu...
As to the lack of a REPL, doesn't Elm have one? http://elm-lang.org/blog/announce/repl
"Before explaining how that program is written, let’s first make a small detour, and introduce the REPL (Read Eval Print Loop) tool, that Elm — like many other functional languages — provides. The elm repl command starts it, like so:"
In November, I started learning PureScript, and I've found PureScript by Example[2] to be an excellent resource. There are a few sections where I think the author moves a little too quickly, but I found that by spending time in psci (PureScript's REPL) I could usually fill in what, for me, were gaps in the explanations. The REPL has further proved invaluable in my fledgling development efforts with PureScript, and the language's ffi is straightforward.
[1] https://en.wikipedia.org/wiki/Type_class
[&] https://leanpub.com/purescript/read#leanpub-auto-type-classe...
Edit: magic translates to instant backend and using just one language for everything. Elm-native-ui seems like a step in the right direction: https://github.com/elm-native-ui/elm-native-ui
obj={key: "val"};obj.key === obj["key"]; => true
and the same in many other languages, like Java, C#, Elm, Haskell (I think) et cetera. The only ones in common use I can think of now is Lisp & C (but of course there are thousands).This pattern of putting the key to get after the object seems popular, but what is the history behind it?
And why is it uncommon to have something like get(obj, key)? Or like in Clojure, `(get {:key :val} :key)`? Seems more consistent, and uses less syntax.
Not Haskell, record fields are accessed via automatically generated accessor functions e.g. after defining
data Car = Car {company :: String, model :: String, year :: Int}
you'd have company :: Car -> String
model :: Car -> String
year :: Car -> Int
> And why is it uncommon to have something like get(obj, key)? Or like in Clojure, `(get {:key :val} :key)`? Seems more consistent, and uses less syntax.Isn't get for collections? While historical javascript conflates records and hashmaps, most languages have a rather strict difference between the two, including clojure I believe. And do provide some sort of `get` for maps (as a free function or as a method depending on the language's base paradigm). I know of few languages where accessing record attributes and map values uses the same syntax[0]
And in clojure get isn't even necessary as most collections also act as functions e.g. `(get {:key :val} :key)` can be replaced by `({:key :val} :key)`, or (for maps only, IIRC integers are not IFn) `(:key {:key :val})`
[0] natively anyway, I have seen e.g. Python libraries where maps were extended to provide attribute-access to their values.
For record lookups, there is no key. What would you pass to the get function as the key? A string? Now the compiler can't reason about it at compile time. It turns into a runtime lookup. In languages like OCaml, there is no reflection, so there is no way to do a runtime lookup of a key. It has to know the name at compile time, and the name is then translated to a byte offset into the record.
Theoretically this could enable a lot of optimization for Elm, but at the moment I don't think the compiler takes advantage of this. It's the same thing that Google's Closure compiler does.
Not that it matters, but how do you reckon
`get(obj, key)` is less syntax than `obj.key`?
1. you have free functions at all (wasn't the case in Java until 1.5 and static imports IIRC, still isn't the case in Smalltalk, and seemingly free functions in Ruby are actually methods of Kernel, an actual free function would be a Proc bound to a global name)
2. you only have free functions, or all your syntax is expressed with free function syntax (which is fairly specific to Lisps, Julia also has generic functions but afaik still uses dotted attribute access)
Politics are boring but they are important. Imagine if all these smart people from haste, fay, purescript, ghcjs could join elm and extend it. Haskell could finally get a (deserved) mainstream role in CS. I don't know Evan (elm's creator) but he seems to have enough charisma for the job. Let's see if he can play the politics.
--optimize-for=size