How Halogen is more faithful to FP ideas

@thomashoneyman was kind enough to give me long answer to a question I had. Posting here with his permission. Thomas said he might clean this up a bit. So you might want to wait for his update.

Question: I am reading some tantalizing things about Halogen, that it is full of deep functional ideas. This could mean one of two things:

  • The comment is from the perspective of JS programmers, and Halogen alternatives based on React have too much of an imperative feel and Halogen is merely more idiomatic in comparison

  • Halogen has advanced FP ideas even relative to typical idiomatic Haskell. If it is the latter, can you please list two or three FP ideas in Halogen?

I think your first option is the closest to the truth. Halogen has a similar philosophy to React with regards to building a user interface out of components which encapsulate local state. Halogen stays faithful to PureScript ideas when implementing the philosophy, hence the note about Halogen expressing “advanced” functional ideas. PureScript bindings to React, however, contort themselves somewhat to adapt to React — it’s ultimately a JavaScript library with JavaScript ideas.

As one example, Halogen supports effect polymorphism — in other words, you can run your components in whatever monad you would like, providing whatever effects you would like. In PureScript this is mostly called the “capability pattern” for structuring applications. You aren’t restricted to Effect. But when you use the low-level React bindings you can only use Effect for your components. And when you use React Basic you can use effect polymorphism, but only because of plenty of boxing and indirection that the library introduces for you under the hood. In Halogen there’s almost zero extra work to make this possible. It’s just idiomatic PureScript.

As another example: in Halogen, you can prevent renders by only updating state if the old state isn’t equal to the new state, or you can memoize data if it’s unchanged between renders, and so on. But React, being a JavaScript library, uses reference equality all over the place. That means that when you use a React feature for memoizing data, for example, you have to be really careful: it’s no longer a simple equality test like normal Halogen or normal PureScript code. Instead, you have to ensure that you return the same reference to the underlying data if it really is unchanged. The same goes for components: you have to be careful not to return a component from a function, because its reference can change, at which point React will think it’s a brand new component.

React will consider these to be three different arrays if you go to memoize them:

a = [ 1, 2, 3 ]

b = [ 1, 2, 3 ]

c = map (\x -> x) b

…but Halogen will recognize these arrays are all equal because its memoization functions use PureScript’s notion of equality rather than reference equality.

These examples aren’t really anything to do with advanced functional programming ideas. They instead are normal ways that PureScript programmers think about code. However, they do show how Halogen naturally does everything the way you would write a normal PureScript program in the functional style.

But Halogen does also introduce some interesting ideas. One of the most obvious is that the entire evaluation model is a free monad, and free monads are a fairly famous, somewhat intimidating functional programming idea. Programming using free monads is simply different from the typical imperative way of writing programs in something like React.

This has become a much lengthier explanation than I intended at first, so I’ll stop here :slight_smile: But if it’s helpful, I started using Halogen with very little functional programming knowledge. I don’t think the framework requires much advanced knowledge of functional programming. But it certainly lets you think in functional concepts when building your user interfaces and has a high ceiling on how far you can push them.


As far as cleanup goes – some of my reply is hand-wavy with regards to the use of reference equality in React vs. PureScript’s notion of equality via the Eq class, and how the React and React Basic bindings handle that. And I would like to clarify that React Basic goes a long way towards making sure you can write idiomatic PureScript.

But the main point is that Halogen stays truer to functional programming ideas than React does, and that alternatives to Halogen based on React (the React bindings, React Basic) are limited to some degree by the fact that React is a JavaScript library. However, all of them allow you to write largely idiomatic PureScript, and Halogen doesn’t require much more in the way of “advanced” functional programming ideas. The use of free monads is perhaps the main one that stands out to me.


I’ll be honest, I do find it a bit funny to consider Halogen “Super FP”. I’ve long noted that it’s essentially pure OO in a typed language. Objects with identity (components, slots), message-passing (queries), late-binding (Free interpretation of components)…


That is a good insight. It explains why I had no trouble understanding the Halogen guide. Very different from my experience reading other things Haskell/Purescript.
I was thinking it was because I was finally getting FP :frowning:


@natefaubion do you have any functional frameworks to compare?

Since you asked for other example of functional frameworks, I’d like to plug Concur here.

The Concur model is very functional and simple. I feel that it represents a fundamental concept in UI construction and most other models (such as the elm architecture) can be easily recreated within Concur’s model.

  1. Widgets are basically monoids i.e. have an empty widget and an append operator for composition (well currently <|>, but I’m considering switching to plain <>).

  2. Widgets are also monads, so you can sequence them (in time) with >>=. This also takes care of event handling.

  3. Finally container widgets are simply functions from children -> widget. So nesting widgets is just ordinary function calls.

So if you can work with function calls, monoids, and monads, you can use Concur. Turns out that these operators are enough to build any UI easily and safely. All the combinators in the Concur library are built using these basic operations.

In addition, all the combinators in the Concur library are abstracted over the monad they use by using a ShiftMap class which represents mapping over encapsulated monadic values. This allows you to wrap widgets in say StateT for state handling and still continue to use the same combinators for composition. This makes it very easy to continue to use your existing FP toolbox to compose larger and larger user interfaces.


@ajnsit, how would rate the type safety and performance of Concur relative to alternatives in Purescript and JS world?

1 Like

With regard to type safety, I’ve been told that Halogen is more typesafe thanks to things like the query algebra. Unfortunately I haven’t used Halogen much (despite having used halogen-vdom and halogen-formless in the past) so it’s hard for me to judge that myself.

Concur is of course typed and would not allow ill-typed programs. However, I believe in an “expansive” view of typing. Type safety is not served by making it difficult to write compiling programs, but by providing simple orthogonal tools that compose together in many different ways, making it so that almost any program written using those tools will do something predictable.

For example, one core concept in Concur is that of “props” which are a way for a widget to hook into user events. Concur will allow you to add any prop to any widget. It doesn’t have compile time checking of props, so you can attach an onchange property to a div. The important thing is that if someone writes <div onchange="..."> they get exactly that ouput in HTML. It may be true that it’s better to signal a failure at compile time, but while it would be good to add these things, they were not my initial focus.

(The upshot of this is that adding support for other backends is easy in Concur. Since it doesn’t really depend on HTML, but works on any tree data structure.)

My aim with Concur was to have an API in which the idiomatic translation from UI spec to code should be obvious, it should be obvious how each part of the UI should be broken down into components, and how to compose them together.


@ajnsit Interesting, I’ll have to take a look at it. I get very tired of all the message passing that is required in Halogen and contained state in the individual components. Hopefully, Concur has better ways to handle composable and module UI elements.