Hyperapp: HOA โ€” Higher Order Apps

Created on 26 Sep 2017  ยท  23Comments  ยท  Source: jorgebucaran/hyperapp

Higher Order Apps refers to the following pattern, in which foo, bar and baz are functions that take the app function and transform custom props into props app can work with.

baz(bar(foo(app)))({
  // Custom props!
})

or

app(baz(bar(foo({
  // Custom props!
}))))

That isn't bad, but this would be much nicer!

app(foo)(bar)(baz)({
  // Custom props!
})

Now, that requires adding more bloat to core! ๐Ÿ˜ญ

So, what's to do and can we find a middle ground?

Maybe!

compose(app)(foo)(bar)(baz)({
  // Custom props & profit!
})

The compose function can be implemented this way! โค๏ธ

function compose(props, enhancers) {
  return typeof props === "function"
    ? enhancer => compose(enhancer, (enhancers || []).concat(props))
    : (enhancers || []).reduceRight((enhancer, reducer) => reducer(enhancer), props)
}

Community Discussion

Most helpful comment

I think this talk is relevant. Michael Jackson (of React-Router), advising against Higher Order Components, and I agree with most of his points. The arguments could apply to higher order apps in hyperapp, in my opinion.
https://www.youtube.com/watch?v=BcVAq3YFiuc

I'm not sure I see the point in supporting higher-order apps? Is there a particular use-case in mind for this?

As @okwolf mentioned, you have to worry about merging props correctly. It re-introduces what mixins were doing to begin with, which seem to be on their way out? I can't keep up :confused:

All 23 comments

How much bloat are we talking?

@lukejacksonn Updated the implementation of compose using a FP paradigm.

How much bloat are we talking?

Not much, 15~20 bytes? Maybe more. But what's not to like about?

start(app)(withModules)(andJazz)({
  // Custom props & profit! ๐Ÿ’ฐ
})

Nothing! I'd happily see ~15-20 extra bytes to have curry-able apps ๐Ÿ˜„

Why introduce a custom HOA mechanism when this can be accomplished in userland?

Also, I think compose could be used or modified to be used also with HOF of h().

import { h } from "hyperapp"
import { compose } from "./compose"
import ps from "picostyle"
import utils from "hyperscript-utils"
export default compose(h)(ps)(utils)
export { h } from "./h"
import { app } from "hyperapp"

// Profit!

@JorgeBucaran so, who's going to publish the package with compose in it? Also how to handle the proper merging of props within each HOF?

I think this talk is relevant. Michael Jackson (of React-Router), advising against Higher Order Components, and I agree with most of his points. The arguments could apply to higher order apps in hyperapp, in my opinion.
https://www.youtube.com/watch?v=BcVAq3YFiuc

I'm not sure I see the point in supporting higher-order apps? Is there a particular use-case in mind for this?

As @okwolf mentioned, you have to worry about merging props correctly. It re-introduces what mixins were doing to begin with, which seem to be on their way out? I can't keep up :confused:

@SkaterDad Exactly why I don't want to add any built-in support.

I wrote compose[1] just to show that it was possible to have a ๐Ÿ’ฏ userland functional "compose" if you ever needed to open pandora's box. ๐Ÿ”ฎโœ‹

@SkaterDad That's a great talk! Thanks

Time to rewrite those HoCs and mixins now. :rofl:

@jacktuck Mixins are just as evil. That's why we are moving away from them in #219! ๐Ÿ„

@SkaterDad Thanks for this link, great presentation ๐Ÿ‘ I totally agree.

I wanted to give my ideas on the subject, sorry if it's not the right place ๐Ÿ™

HOCs never really replaced mixins as they are not composition. It's a pattern very similar to the good 'ol decorator pattern, which is basically inheritance inversion. I'm under the impression that the FP trend has also created a new "one pattern to rule them all" trend. Functional composition and object composition are not the same... I'm not buying the fact that we can treat our views like pure functions for two simple reasons: lifecycle events and the complex nature of a state tree (as opposed to primitives manipulated by higher order functions).

@JorgeBucaran Mixins are not so evil, they just need to have an explicit API associated to them. Simply merging objects is evil for obvious reasons (name collision, obscure dependencies, ...). But the idea of assembling an object with smaller ones is powerful (and 100% tested by nature ๐ŸŒด).

The render prop pattern is very interesting though. It solves problems but creates new ones imho. It makes the code really verbose and you end up having pure behavioral components in your component hierarchy, which gives me the impression of mixing apples and oranges. Why should everything be labeled as components?
That said... it would be trivial to implement with HyperApp.

@ngryman That's a great summary and render prop pattern can definitely add verbosity (esp. when nesting components).

As with most things there are tradeoffs.

For me, i think render prop pattern is the lesser evil of HOCs and Mixins.

@JorgeBucaran this is being left out of 1.0, correct?

@okwolf builtin HOA sugar? Yes, out.

If you want to create HOA I encourage you give compose a shot.

app(a(b(c(props)))) โ†’ compose(app)(a)(b)(c)(props)

@JorgeBucaran If we're gonna get rid of mixins, how do you expect me to write anything more complex than a hello world using Hyperapp? My current work project uses 50+ mixins right now and it's an incredibly useful pattern for me.

I looked into the mentioned issue #219 and couldn't find what you were talking about. However, I noticed that we might solve the global vs local state problem by binding the function's this to the local/scoped state (e.g. state.some.scope) while state always contains the global scope. That'd be an acceptable compromise IMHO.

@dodekeract one thing you could do is make a HOA that brings back the mixin pattern again. That's kind of what I'm doing with hyperapp-partial, because I'm kind of in the same boat as you wrt mixins/partials. Haven't published anything yet. Currently working on tests and docs, but the implementation works so I know it's possible.

I have an app that uses ~10 mixins (they were still known as plugins back then).. looking at them now it seems like I will actually have to remove a lot of code in order to make them work with the new arch. @dodekeract I'm not saying it wouldn't be hard work if you have 50+ but I think it could be done.. very little functionality has been removed in the alpha, just the wiring has changed. That said, if you are so heavily invested then stick with the version you have. No need to upgrade, it is working for you.

@lukejacksonn How is no functionality removed when my mixins can't communicate with each other anymore? How am I supposed to split up code into small files with a specific purpose if these files are completely isolated from each other and can't call each other?

I agree that the removal of custom events does constitute a significant removal of functionality. I'm also disappointed to see them go, but I understand the reasoning. Fortunately it's not that hard to bring them back (again, with a HOA).

My current approach is to make a HOA version of partials so my current apps can all work with minimal to no changes. Then I will solicit suggestions for possible ways to restructure without custom events, and evaluate those approaches. But I have a feeling I will stick with custom events, even though they're no longer in core.

@dodekeract Hyperapp is intended to be used for building frontend applications from simple parts using a functional paradigm. I am referring to the kind of simplicity that Rich Hickey of Clojure espouses where each part of your app lives independently and can be composed together with as many other independent pieces as you need. This is in contrast to complex OO style MVC where the different pieces are so tightly woven together that you can't think about any one part in isolation without considering the other parts that it touches. Joe Armstrong (creator of Erlang) put this best:

Because the problem with object-oriented languages is theyโ€™ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. ๐ŸŒ๐Ÿฆ๐ŸŒด

Perhaps it's worth rethinking how code is organized if there's a heavy amount of communication needed between different state slices ๐Ÿฐ . That is a sure sign of complexity which I would personally attempt to simplify even if Hyperapp didn't enforce this state separation on me. Remember that for the purposes of rendering the view there should be a single state tree and the view has access to the entire state to create the view anyway you like, including reusing the same state in as many components as you wish. This should only affect actions that need to update multiple slices of state, which should be minimized for the reasons listed above.

However there are legitimate reasons that an action in one part of your app might need to affect another part, and for this purpose we are providing the hooks API. You can think of them like the middleware Redux supports that allow you to subscribe to actions that happen across your entire app and let you call other actions in response to them. This can be used for many of the same use cases as custom events used to be used for, but under a single unified API for actions. (We are working on a replacement for hooks using HOAs, the original topic of this issue)

Following through on this simple approach to building apps is still a challenge and learning experience for me at times. But I can promise that if you are willing to give it a try, the results will pay dividends and your projects will be able to scale without hitting many of the roadblocks of complexity that always creep in eventually.

@dodekeract it is now even easier to split code up.. they are not isolated and can call each other. Assuming you have all your mixins nicely namespaced already (otherwise you must be going insane) then you can simply put them all one level deep in your app state/actions and use them almost exactly the same (they can access all the app state and every action). If you need to break out from a deeply nested action to have an effect on the app state then you can implement your own emit function (or use one of the many event emitters out there) and setup subscriptions.

Closed by 0.14.0 :tada:

Sign of complexity

Well, the project that I'm doing at work is very complex. I don't try to hide that, it's exactly the reason why I don't think I can port this project without basically short-circuiting hyperapp's included state management.

For example, it consists out of multiple instances of hyperapp that are spread across iframes with different purposes. E.g. one of our iframes handles WebRTC communication and video streaming. Another one handles communication with a persistent part of our API. The reasons behind why we chose iframes are relatively specific and I won't go into detail here, but that's not changeable with current web technology.

In addition to that, we can't trust the top-level frame, as it's cross-origin, so messages passing between these frames need to be signed to prevent forgery. That's why one part of that app is the .message namespace, which includes some handy actions for composing, sending, forwarding, verifying messages and combined with .crypto also generates the signature passwords.

So, these parts of the app are separate, but still need to call each other. .crypto is a utility mixin that handles automatic signing, and verifying of messages.

Another part where it's not trivial to separate is our WebRTC code. There are actions like actions.rtc.peer.create() which creates and setups the RTCPeerConnection. This needs to be called from actions.rtc.mode.start(), which starts the whole WebRTC initialization process.

Now, the thing is that global actions aren't actually all I need, as we also have state.userMedia.stream for the local webcam stream. This (of course) needs to be accessible to actions.rtc too, so that it can send the stream via WebRTC.

That's only two of the easiest to explain examples. Huge parts of the rather big codebase need to communicate with each other as this project isn't a simple UI, it has app-like complexity and will be used similar to an app when we release it.

I can promise you if you're willing to give it a try

My willingness to do that is one of the main reasons why I'm using hyperapp since 0.0.6 (IIRC). I don't think there's a good, simple solution for this complex problem. In fact, applying hyperapp's way of thinking helped me out a lot in a lot of parts of the app. In some other parts, like the RTCPeerConnection management (which is rather complex), I completely disagree with the proposed solutions and as I can't maintain a 2000 LOC file, I can't merge all of our RTC & userMedia mixins.

@dodekeract #406

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jorgebucaran picture jorgebucaran  ยท  4Comments

ghost picture ghost  ยท  3Comments

Mytrill picture Mytrill  ยท  4Comments

icylace picture icylace  ยท  3Comments

VictorWinberg picture VictorWinberg  ยท  3Comments