The DOM syntax makes perfect sense, but you have to realise it's not a 'transformation' in the sense of taking a stream of text and converting it into a stream of HTML for the browser. It's just Elm code that returns DOM nodes.
The HTML functions in Elm tend to take two parameters: a list of attribute values and a list of child DOM nodes. Again, this is all code. The list values you specify are again just functions that get evaluated.
It's a little less obvious because of the syntax style of Elm, but it does let you write the function calls out in such a way that they express that tree structure.
It bears repeating: your Elm div example, at the top level, is a single function call with the first parameter '[]' being an empty list (because you aren't assigning the node any attributes like a DOM id or CSS class) and the second parameter being a list that'll be made up of the DOM nodes that result from each of the function calls within it.
This was one of the biggest mental roadblocks I had to get past when learning Elm. Just forget the idea of HTML and think instead of DOM tree-of-nodes and a lot falls into place. The 'view' function you write will return a DOM tree data structure which is then taken and applied to the browser document. You pass in your model as a parameter to that function and then other functions you call within this DOM-generating code you've written will use that data to return different DOM contents depending on it.
Yes I do understand all that, as I'm a React dev, in which render() methods and 'pure function components' work in exactly the same way (take props and state as input and return dom-creation-calls).
That is why I used JSX as a counterexample. Basically I'm questioning Elm's choice to stick with the raw dom-creation-function-style vs adopting a JSX equivalent.
I'm suggesting it doesn't 'work fine', because syntax matters.
Lisp-style 'S-expression' syntax for describing documents never took off. We have to take something from that - it seems the vast majority of developers don't enjoy that syntax, irrelevant of how 'simple' its just-functions nature makes it.
JSX also 'targets DOM nodes', but I don't see what that has to do with anything.
The closer correspondence between how JSX looks, what you see in your browser DevTools, and how it seems that most developers prefer to mentally model documents ... is where the win comes from IMO.
The reality is the vast majority of React code I see uses JSX, even though it's optional. It's been hugely successful in React world and I don't think that should be ignored by Elm.
It's an interesting balancing act. I think when most devs look at JSX for the first time they instantly recoil at the rebellion against years and years of "separation of concerns", yet as you say it's a syntax that devs have embraced regardless.
I love Elm and think it or something very close to it should become way more mainstream, but I do find its bracket-heavy DOM syntax off-putting in comparison.
EDIT: That said, I do agree with the leading commas in laying out models as opposed to the trailing. Scanning the models of an Elm file is far faster than, say, a JS file, subjectively at least.
HTML and XML for describing documents also never took off; they are largely generated from something else rather than hand-edited.
Thus, no structural, recursive syntax for document editing has "taken off".
Word processing is still done in Microsoft Word by most of the planet. HTML e-mails are written in some WYSIWYG client program. Web forums use variations on markdown (with raw HTML only as an escape hatch).
People do work with *ML by hand, but largely reluctantly.
If you do have to roll up your sleeves and work with the serialized syntax, you're far better off it its is S-expresions.
The reason for the commas at the beginning, is that it is easier to add a new line at the end. That is, if you forbid a "last comma". Traditional languages don't let you do
Yep, I get the preference over a lang that can't do trailing commas, which is why I used the trailing comma style as the counter-example. In a compile-to-JS language it's hopefully possible to make trailing commas legal. They've been legal in JavaScript itself for a long time (ES5) [0]
> Also, it is much easier to look at this [...] and see if all the commas are actually present.
ESLint can trivially lint that you haven't missed a comma (including the final trailing comma). The style guide/ESLint config I use (Airbnb's) enforces trailing commas. If ESLint can do it, Elm should be able to.
They might have been technically legal since ES5, but they were only finalised this year (ES2017, with function params), and still don't seem to have full support. I've been burned a few times by that: I know that MDN article cited says they are, but code with trailing commas (esp objects) breaks in eg IE without transpilation
Yeah as the article says they were only added for object literals in ES5, which is the main place the 'issue' comes up, and where I was making the comparison to Elm. That works in IE > ~7/8? without transpilation.
> In a compile-to-JS language it's hopefully possible to make trailing commas legal. They've been legal in JavaScript itself for a long time
Well, Elm is not trying to be JS, it's trying to be better, and there's really no reason to need trailing commas. I'd even argue that it argue it leaves a message of mixed intent "did the author want to continue the list?" and needlessly enables duplicate styles for the same one thing.
That's a tenuous argument. I've yet to meet a developer who thinks a trailing comma is the slightest bit ambiguous when the next character is a closing bracket.
Regarding "weird bracket soup", isn't the HTML version "weird arrow soup"? I suspect you're just used to seeing HTML, so you forgive it for its ugliness and oddities.
The main difference is that the Elm "bracket soup" is just a composition of functions and is subject to some type checking by the compiler. Elm view code as such can easily be restructured and refactored using the simple laws of function composition, which are very powerful, especially in combination with a strict type system.
As I alluded to I think the HTML version is less weird because it is close to what you're going to see when you open your browser DevTools. So the JSX/HTML style requires less mental gymnastics overall.
Also there are some things about Elm's DOM style in particular that are especially awkward, compared to other function-style dom creation syntax [0], like the [ ] everywhere.
> The main difference is that the Elm "bracket soup" is just a composition of functions
So is the JSX counterexample I gave, (it de-sugars to function calls), which is why elmx [1] can exist, and seems like an improvement to me.
Prior to Elm I spent 5 years building front end apps using ember.js(handlebars templates) and then JSX with React. From memory it took me about 2 days to get used to Elm's approach. Once you get used to have the full power of a programming language to build your view going back to using templates honestly feels painful by comparison. Aside from the power of having a full language available you also get the benefit of static typing. Refactoring your views with the same ease that you do with your normal code and with a compiler to watch your back is liberating. I wasn't aware how cautious I was with refactoring view components until I tried the alternative.
JSX is a language extension which desugars to JavaScript. It is not the same as 'going back to templates'.
> Refactoring your views with the same ease that you do with your normal code and with a compiler to watch your back is liberating
I have this experience with JSX (which I didn't have with string template libs), and I also don't see that an Elm equivalent like elmx would take anything away from that experience in Elm.
I still think you're trying to prematurely optimize against your very first impression of seeing a new syntax for a familiar data structure. I've actually written several thousand lines of Elm code for a real internal tool at work, and elmx feels like it just complicates the view code with virtually no benefit. I think it's solving a problem that doesn't exist -- and not at zero cost.
The result won't be understood by elm-format. Compiler errors will exist at the level of Elm, not elmx. It's something extra that has to be learned by any other developer trying to work on the code -- and they'll still have to understand the pure Elm code that the elmx compiles to as well. Plus it has limitations like not being able to nest code interpolations.
This is getting more heated than I care for, so I'm going to bow out. As for your last sentence, just read the README for elmx:
> Non-recursive interpolation: currently Elm code interpolated between { and } is not recursive (i.e. is a regular grammar not a CFG). This means that you cannot include curly brackets inside curly brackets.
This is of course just a limitation of the implementation, not the idea.
There are reasons for all these, the first two are both to optimize for VCS changes, since adding something to your first to examples would now only generate a minimum diff[0]. That, and considering Elms roots in Haskell, this is the common style for things there.
The last one is because all html in Elm are just regular Elm functions. This means no special syntax to manipulate html, and you have the power of all of Elm at your fingertips, without shoehorning it into a new syntax. [1]
What if you want to change the first item of a list? If you're going for smallest diff (which I'm not even convinced is a worthwhile consideration in language design), then allover's example is clearly superior to Elm's current convention.
The comma first is not with language design, it's a convention from FP langs (at least Haskell).
>What if you want to change the first item of a list?
Html.program
{ view = view
, update = update
}
to
Html.program
{ view = newView
, update = update
}
Leading comma vs allow a non-terminating comma both leads to the same diff of just the one line.
I mean, there are quite a bunch of languages that don't support non-terminating commas, which is really a bit of parsing that hides intent.
Other than that, bashing a language for having the convention (that is not forced by the language but by... convention) is quite ridiculous. The HTML comment is a valid point to raise, but there is quite a good reason for having it be native Elm, which brings a ton more benefits than having JSX'esgue syntax.
I meant what if you wanted to add a new item to the front of a list or change the order of items in the list.
[ 1
, 2
, 3
]
to
[ 0
, 1
, 2
, 3
]
diff
- [ 1
+ [ 0
+ , 1
, 2
, 3
]
> Other than that, bashing a language for having the convention (that is not forced by the language but by... convention) is quite ridiculous.
It is forced slightly by the language as trailing commas are currently invalid syntax (though this is on the roadmap to be fixed). It's also more than just a convention, as it is explicitly recommended in the official Elm docs.
What actually distract people are boilerplate/composability, lack of fancy types, package publishing openness and slow-paced community. Don't get me wrong, I like Elm, this is just my observation.
The boilerplate issue is frustrating. It sometimes seems like the Elm resources out there all stop at toy examples; SPAs with only three or four different screens without components that need to manage internal view state.
Really I think it's just the stage of adoption that the language is in. It really is cutting-edge in that way and everyone is figuring out the best way to do things. It's early, early days.
In my current project, I've used templating to solve this. I ended up with a huge Elm main program that was 90% case-statements to call the appropriate update, view or other functions (eg- passing JSON data from the back-end server to the appropriate places). I got cranky enough with it that I made a templates version of it using the doT.js library and a script.
Now I can specify the subscreens I want in my application in a JSON file and have a script take that and my Elm code template and spit out a perfectly readable main.elm file which then gets compiled as normal. It's actually turned out really well for my purposes.
<div> is html while div[] is elm code. Are you asking why not support html in the elm code? IMO its much much nicer to just program the DOM in elm than translate between elm and html.
Also, I type my code any old way such as { counter: Int }. When I save elm-format reformats automatically.
I am sure one gets used to it after some time, but prepending lines with comma, just because it makes it easier to add new lines is so so ugly and takes away from the main intent for newcomers.
This is akin to starting each sentence in English with a dot!
Reading the comma style, to me, is cleaner. But typing it while writing the record type is urgggg! It is this cycle: type words -> enter -> repeat tabs -> type comma -> type words Or you will have to keep hitting `save` command for auto-formatting.
I don't read code like I read a novel. I read it in an attempt to understand the writer's intentions, and what the code is meant to accomplish. I find the leading commas to be far easier to scan than the other way around.
You can use elm-format and never think about formatting again.
Instead of mixing html and js why not use function calls for everything. I believe there would be no jsx if javascript would allow function calls without the parens.
That philosophy works better when the style you enforce is, if not happily accepted, at least not moderately controversial. Otherwise many developers will choose simply not to use x-format—or worse, be turned off from the language.
In such layout-block languages (without curly braces and semicolons for denoting blocks and "statements") it shouldn't be a big deal one way or the other anyway..
Yep, I think the emerging tradition with Elm is just to leave it to elm-format. I wonder if tooling may end up being the answer to composability and boilerplate too, though I'm not sure quite yet how.
There is no boilerplate in Elm if you don't write self-contained components and you really shouldn't. You end up with huge model/update/view functions that you split down into modules. Here is an example app - https://github.com/knewter/time-tracker.
Really cool app! I'm going to study this. I'd love to put the app I'm working on up as an example to share lessons learned but it's commercial work, so I'll have to make another hobby one in the future and use that :)
I alluded a bit to it in another comment here, but I'm not entirely convinced that the "you shouldn't write components" is a good design target overall vs. just being a necessity because of the limitations of Elm the language right now.
It probably hinges on what you mean by 'component'. When I say 'component', I mean a bit of UI I'm planning to reuse across multiple pages, where there might be some private model, where I would not want the internals of that model duplicated across and polluting otherwise clean page models.
Here is an example that comes to mind: I have common functionality across pages in my app where I want to let the user edit a database field.
To do this, the user can select the list of fields from a drop-down or they can type the field name into a search box, with choices being displayed as they type in real-time; they can then click on a choice and edit that field.
To submit the edit, they click an 'Update' button.
Now, this functionality is shared across different pages in my app. None of these pages should, IMHO, necessarily have to care about the intermediate states of the field-list as it is auto-completing. I simply want a function I can call from any view like this:
renderChoiceThingy : List FieldChoices -> ChoiceThingyModel -> msg -> Html msg
renderChoiceThingy fieldsToPickFrom model actionOnSubmit =
....
Not only might I want multiple instances of this same thing across many pages, but I'll also want multiple instances of it on the same page sometimes. For example, let the end-user build up a list of changes and then submit them all in a batch or cancel them all at once.
Without treating this like a self-contained unit, I'm now duplicating model bits in all my pages that use this function. So it's nice to wrap that control up into a module, with its' own 'update' and 'view' and have appropriate parameters to allow the proper message types to work.
Another example (not one I've implemented); say you have an interactive colour picker, that you can click on an icon to exapnd, select the colour you want by changing sliders interactively, then have the picker disappear and the page know what colour you've selected. To me, the containing page shouldn't care about all that slider stuff and all the messaging and updating that goes on, just to draw the picker.
Would love to hear other Elm devs thoughts on this. These are the kinds of examples I don't see directly addressed when people say "don't write components". Though I suspect, having not used other 'component' frameworks like React, that people might generally mean something slightly different to what I'm thinking when they say "no components". Really interested to find out what others think about this.
If you haven't seen it, take a look at elm-sortable-table [0]. It's an example reusable view, written by Evan, that's meant to demonstrate exactly what you're asking about.
Commas-first give you an obvious visual clue when you forget a comma and let the reader immediately know that the current line is a continuation and not a new expression.
It's a shame that all that hits me in the face with Elm code examples lately is awkward style conventions. Why do they have to be so user hostile?
Trailing brace on the next line even for this?
Instead of: Comma first, when trailing comma is so much more intuitive, why this? instead of: (... which makes for better diffs too). And weird bracket soup for DOM: ... when it's going to be translated to DOM anyway, why not? This is needlessly distracting from all the things that are nice about Elm.