Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I've tried to use Elm and The Elm Architecture on a project before, but I gave up.

The Elm Architecture makes it very easy to create small type safe components, but it forces parent components to manage the state + structure all children components. This makes it hard to use a component and to separate concerns.

In order to create a list of stateful children, you must:

1. Add a list of the child states to the component state, event if the parent component doesn't care about the child's state.

2. Handle adding + removing child state's manually

3. Map all scoped child state updates through your reducer, adding a constructor to your action type

4. Finally, map each of the child state's to the component's view.

You end up big types and match expressions that just manipulate lists. I have yet to see a FRP library that allows you to just use a component without introducing boilerplate.



> In order to create a list of stateful children, you must:

This is trivially debunked: here is a 4,000 LoC open-source Elm SPA with small, type-safe reusable views that have state and don't do all this. :P

https://dev.to/rtfeldman/tour-of-an-open-source-elm-spa

Plenty of counterexamples in there.

Here's one. https://github.com/rtfeldman/elm-spa-example/blob/master/src...

Here's another. https://github.com/rtfeldman/elm-spa-example/blob/master/src...

Here's yet another. https://github.com/rtfeldman/elm-spa-example/blob/master/src...


The parents of each of these elements have to manually map the state changes, they have to be aware of the internal state of the components, just as I was saying.

Check this out: https://github.com/rtfeldman/elm-spa-example/blob/master/src...

Go to the bottom, see how it manually maps the child events of the feed update? You have to duplicate that code for every single child component. All the other steps I described are there also.


Again, there are lots of counterexamples to what you're claiming. The technique you're saying "must" be done in "every single" case came up a grand total of once in the entire code base: the file you linked to.


Yeah.

My top 3 complaints:

1) The crazy boilerplate, the Elm architecture forces you to store all your app state in a single atom. In my experience, this doesn't scale well and is bloody annoying (see, even the redux author say you should never store everything in the single atom/store); Also, when should state be cleaned up? When the component is shown again, but then you have stale state in the meantime; or when the component is removed? before or after it's being transitioned out? (you don't want to see a flash of empty data)

2) The snail pace at which Elm evolves. I will have delivered 3 or 4 apps for my clients by the time the single Elm maintainer added 5% of the WEB API as native Elm modules. By the time he's done, new WEB APIs will have to be covered. It's never going to end. That means, a complex Elm app always has javascript. In that case, I'd rather just use typescript and nothing else.

3) The religious focus on purity. The elm maintainer is an haskell user, and it shows. Can you believe that to read the current time or generate a random number, you must be within a component's update function, ask for one or the other and you will get your result later, asynchronously? I know some people love that their entire program are fully pure in a mathematical sense, but this always struck me as having way more cons than pros.

Also, there is always "just one way to do this or that, all the other ways are disapproved by the Elm creator" The environment/community is so closed! If the Elm maintainer did anticipate your very specific need (low chance, he can't do everything by himself), the answer is: "always use that, you should never use this other thing, it's very dirty", if not, "just use this JS hack instead". This is good for beginners seeking very strong guidance, not good for power users trying to deliver stuff fast.

You can tell Elm is still a beta product; it has lots of cool stuff, but even more rough edges.

Otherwise the language is kinda nice and simple (some would say too simple, I don't know If I agree; maybe) and is great for teaching FP to first timers.


> The religious focus on purity. The elm maintainer is an haskell user, and it shows. Can you believe that to read the current time or generate a random number, you must be within a component's update function, ask for one or the other and you will get your result later, asynchronously?

You say this like generating a random number is a small deal. In a sense this is very true! From the machine's perspective generating a random number is a quick operation. However, from the perspective of maintainers who come after you losing determinism is no small thing, and IMHO deserves to be marked clearly in the type system.


I also respect this view. On the frontend, as long as I have a decent type system, I never needed to also encode effects using the type system, so I'd rather not burden the team with this if the ROI is very small.


1) I don't see a problem with this. If you have stale state, but no one sees it, who cares? Same thing regarding cleaning it up, if no one notices when, who cares? The great thing about storing all the data in a top-level datastructure is that you could dump that entire thing into JSON, send it along in bug reports, and then you can see the same state the application was in when a bug happened. This is gold!

2) In my experience, with typescript/javascript you instead get to deal with the fact that in the same period of time the API for webpack has changed, the api for react-router has changed twice, etc. Slow is not necessarily bad, especially considering the reason things are slow is because of the focus on doing things right. Also, just because you can't escape a tiny amount of javascript, having the rest of the application in Elm isn't worth it? How strange.

3) I admit that it sometimes is a bit annoying that you can't get things like the current time --right now--, and instead have to thread that through the update chain. But overall I find this to be a good thing. It's great when learning new code bases to easily identify where side-effects can happen just by looking at the type signature.

> Also, there is always "just one way to do this or that...

As a power user, this is the best thing about Elm. The fact that javascript isn't like this, is why I never want to maintain another javascript project that I didn't personally create, ever again.

> You can tell Elm is still a beta product;

Elm is a alpha product, so this is really a great compliment.


Thanks for the different perspective.

And no, I wouldn't want to maintain a codebase written in a dynamic language ever again too :p


More specifically, to quote the Redux FAQ entry at http://redux.js.org/docs/faq/OrganizingState.html#organizing... :

> There is no “right” answer for this. Some users prefer to keep every single piece of data in Redux, to maintain a fully serializable and controlled version of their application at all times. Others prefer to keep non-critical or UI state, such as “is this dropdown currently open”, inside a component's internal state.

> Using local component state is fine. As a developer, it is your job to determine what kinds of state make up your application, and where each piece of state should live. Find a balance that works for you, and go with it.



The Elm architecture is explicitly not FRP. With FRP, you could include a component that changes pretty easily; for example, with GHCJS and reflex, there is a function called dyn that takes a Dynamic widget (ie a signal of widgets) and turns it into a normal widget:

    dyn :: MonadWidget t m => Dynamic t (m a) -> m (Event t a) 
The types take a bit of getting used to (reflex-frp is not nearly as beginner friendly as Elm), but once you are used to them it's incredibly expressive. Being able to just wrap a changing widget into a normal widget without needing to change the rest of your code is a big part of why I ultimately like reflex's model far more than the Elm Architecture.


> I have yet to see a FRP library that allows you to just use a component without introducing boilerplate.

I'm the author of https://github.com/calmm-js.

I wouldn't necessarily describe it as (pure) FRP, but the combination of techniques used in Calmm pretty much eliminates all boilerplate: in the limit, a truly useful component can be just a single function with a body of a single line of code. Using that component then is just a matter of instantiating the component with desired parameters (no boilerplate). The lack of boilerplate makes it practical to eliminate even small scale repetition with local components.

Parameters to components in Calmm can represent read-only (Observable) or read-write (Atom :> Observable) state. RW state can be sliced with lenses and be used as RO state without glue. RO state can be arbitrarily combined to compute desired properties. Thanks to being able to effortlessly slice RW state, components can be easily made independent of the application state as a whole (IOW, a component can be made to take only just the state it needs as parameters) and be made truly reusable.

Calmm also makes it simple and easy to create both stateless components (consider #1) and components that can optionally use local state so that the user of the component can actually choose whether the component uses local state or a slice of global state.

Calmm also primarily guides you to use stateless, time independent, continuous, observable properties (combine) rather than streams of discrete events (merge + scan) (see #2) which actually introduce undesirable local state and force you to consider timing dependencies again.

#1) https://awelonblue.wordpress.com/2012/10/21/local-state-is-p... #2) https://awelonblue.wordpress.com/2012/07/01/why-not-events/


I'm curious if you've ever looked at optics-based systems for component coupling?

Personally I think that the entire top-down state tree is a bad idea, but I'm willing to be convinced. I'd much prefer a modeling like the actor approach.


Keeping all external state in a db like abstraction, and having any component, regardless of view tree depth, be able to query against it for data that it needs, and have it update when that data changes, is the way to go, and it's the future.

Applications developed this way are very easy to change and update. Deleting a completely self contained component, not having to update the parent by removing the wiring of the data, is a blessing.


Google isn't finding anything regarding component coupling and optics. Do you have any links that introduce the concept?


A lens is an object that has both read and write access to a property of an object (or to a path into a tree of nested objects).

Rather than passing parts of the model to the children (as you'd do with React props), you pass lenses into said parts.

It enables child components to read/write to the global store without knowing its structure. As long as it receives an appropriate (set of) lens(es) it is happy.

So, for example, if you have a list of strings, rather than passing the nth string to the nth component, you'd pass a lens to the nth string. The component can then not only read but also write to its slot.

Lenses can also be composed, so you can create them on the go as you'd pass sub-props to grand-children.

https://github.com/calmm-js/partial.lenses <= a comprehensive lens library

http://ramdajs.com/ also has lenses.

These libs are strongly influenced by the FP world. The underlying data structures are immutable, writing to a lens creates a new version of the store (as efficiently as possible, by recycling intact objects).


So basically, it's like using dependency injection, and what you're injecting are higher order functions for the getters and setters for whatever properties you need. Is that right?


Do they need to be functions? Because this seems a lot like handling [:a :keyword :vector :for :navigating :trees] in Clojure and passing it to either update-in or get-in functions, which is a pattern ubiquitous in re-frame, but not sure is can be called a lens).


What you're saying sounds similar. But my question and your post are both missing the "composition" aspect. Somehow lenses are composable, and I guess to grok that we'd need to look deeper.


Sounds about right.


PureScript's thermite is what I had in mind, but yeah this is good.


Thank you.


I wrote about this some time ago. https://arianvp.me/lenses-and-prisms-for-modular-clientside-...

However, recently I discovered Halogen [0]. It is very similar to Elm Architecture, however, every component has its own _internal_ state, so you do not have to thread all state changes to the top-level component. It does this by existentially quantifying over the State [1].

Halogen is written in Purescript, which is a transpiler for javascript (yes transpiler, not compiler). Which means you basically get javascript semantics with a haskell typesystem + very good interop with JS, and no runtime system. The code purescript generates is actually readable. [2]

[0] https://github.com/slamdata/purescript-halogen/tree/master/d... [1] https://wiki.haskell.org/Existential_type [2] https://purescript.org/ [3] http://try.purescript.org


> Halogen is written in Purescript, which is a transpiler for javascript (yes transpiler, not compiler).

Transpilers are a subset of compilers; it is impossible for "yes transpiler, not compiler" to be true.


Thank you for being a force for sanity in the universe. "Transpiler" is a distinction without a difference to add yet more jargon to our already cryptic profession.


For what's it's worth, I never found a use for optics.

The idea is that you declare read/write optics than you then compose and reuse.

But on a web app, say, in how many places can you update a person's address?

Writing non generic code that update it in a non generic way (throw away code) is much less ceremony than defining an optic and only using it once.


In this case the optics are used as a way to select state in the coupling construct. You don't use them to read values out of the form, you use them to explain to the components how to pull or update their state out of the shared construction.



I've seen optics for Cycle.js but I haven't found one yet for Elm.

https://github.com/staltz/cycle-onionify


> optics-based systems for component coupling

Do you know of any good resources on that? Or projects using these concepts? Sounds interesting.


Calmm-js is one of these: https://github.com/calmm-js


This is exactly the react/redux/flux architecture everyone is raving about.


> I have yet to see a FRP library that allows you to just use a component without introducing boilerplate.

Given all the benefits of Elm, 6 lines of boiler plate (one line added in model, 5 lines added in update) is a small price to pay. Especially considering the small amount of components (in contrast to React, not everything is a component).


Same here, any time we wanted to use Elm for a production system we gave up. It brought more problems to the table than solved. Maybe later it becomes something more usable.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: