Selecting a platform

JavaScript vs Elm vs PureScript vs GHCjs vs Scalajs

I’ve attempted this post 5 times now, and have come to the conclusion that to start off, there needs to be a version that is as neutral as possible. There are not many people who have worked on production applications in multiple frontend FP systems, and lived to tell the tale. So here goes, Fresheyeball’s guide to selecting a functional programming platform for the browser. I’m going to assume you are already sold on functional programming and it’s benefits in a UI development context.

This is about building purely functional user interfaces.

If you are looking for me to say that using Rx.js + Angular is functional programming, or endorse Reflex, or hedge in some other way, the answer is no. This is about purely functional programming, which means I’m not even going to consider a system valid without referential transparency and IO managed in some way. So yes, all flavors of Lisp are off the table as well.

JavaScript

Here are some reasons to choose JavaScript over purely functional transpilers:

Here are some reasons to not choose JavaScript:

There are a few ways you can do FP in JavaScript directly. Enforcement is a real challenge, and will require discipline on the part of developers.

Here is how:

Linting

First we need some way of removing all the impurities from the JavaScript language. Luckily we can do this with some very strict configuration options on eslint. For this I recommend Bodil’s cleanjs. This marries together some existing configs out there, adds some extras and ultimately accomplishes to goal of removing all the impurities. Unless your code can pass this linter, it’s not likely to be pure.

Types

After wrestling with this one for a long time, I can safely say there is no type system available for JavaScript that is really sufficient to guarantee safety. That said you should use something if possible. Here are my take aways. There are type kinds of type checking available for JavaScript, compile time checks and erasable runtime checks. If you choose a system that can do both, do both.

Compile time checks

Since TypeScript is the only worth while compile time checker. If you are already in a large JavaScript code base, abandon all hope for compile time checks now (refactoring is obviously possible, but usually infeasible). If you are in a greenfield, use TypeScript. If your project is already TypeScript all the way down, don’t move, unless it’s to a purely functional language.

Runtime checks

You will need to find the one that works with your environment. Runtime checks are mostly good for contract enforcement with the server. If you can get runtime checks setup do so. Consider typify or sanctuary-def.

Libraries

Required

Worthy

Regrettable

Recommendation

Use TypeScript 2 and definition files for type checking, both at compile time and runtime. Use definition files to ensure all outside libraries are also checked (you may need to write some of these yourself). Pass the compiled JS to eslint to check for impurities. All IO actions should take place within the Ramda Fantasy IO Monad, and runIO should appear in your code once, ever. This is the best you are likely to get, and you know what, it’s pretty reasonable.

(Full disclosure, I’ve used each of these technologies individually and in some combination, but not the specific combination in the recommendation. There may be some unforeseen challenge in mixing these tools. That said it’s the best we are likely to get.)

Elm

Elm is a unique language, with it’s own voice and abstractions. Elm is purely functional, and arguably the purest option available. Here are some reasons to choose Elm:

Here are some reasons you might want to avoid Elm:

Pros unique to Elm

Elm is the most mature, of the FP transpilers. This means you can expect a broad ecosystem of useful packages, and advanced tooling. Elm also enforces The Elm Architecture (or TEA) on you from the compiler level. Now there is nothing to stop you from using the TEA pattern in the language of your choosing (so long as it has type unions), and many often do, but choosing Elm means reaping the maximum benefit from the TEA pattern. Benefits including time travel debugging, incredible performance (courtesy of TEA specific compiler optimizations) and immediate code familiarity (think of the nice bits of RoR). TEA also generally yields very nice simple, non-Monadic code.

Elm’s learning curve is extremely shallow, in part because of a reductionist philosophy, and a simple type system. I have personally witnessed a Java developer modify Elm code successfully, thats amazing for when you have to work in the mainstream. Elm can also be used as a gateway drug, on a pipeline moving people toward more advanced FP languages. Considering the small size of the Haskell community, and the large buzz about functional programming in the JavaScript community, this is a good thing.

TEA enforces a separation between stateful logic and view logic. This is good or bad for development depending on how you resonate. But one thing it’s definitely good for is designers. If there are people at your company who write Html/Css only, Elm is a great pick. Most designers can quickly and easily understand Elm view code, and edit that code successfully.

IO is extremely well managed. This is part of why Elm is so incredibly pure and safe. There is nothing like liftIO or unsafePerformIO or any such thing in the language. Elm code is almost never Monadic (this should be a good smell nay saying Haskellers), and since we can’t cheat, this means IO is never performed in the middle of code. This makes it trivial to track down side effects.

Enforced TEA also means that the application has a single source of truth representing all state in the app. One weird benefit of this design decision is that the complete state of the program can be serialized, and de-serialized. Making restoring state, and QA a breeze.

The TEA specific benefits are possible outside of Elm, but are not guaranteed.

Cons unique to Elm

TEA is not composable, but through brute force. Many attempts have been made to make TEA compose, with limited success. If you want all the benefits of TEA, you are stuck building and maintaining some giant types, and writing a ton of boilerplate. The ways around this problem are fairly weak, and amount to, “don’t write components” (IE don’t intend your code to be re-usable), use lots of dictionaries for state, attempt your own code generation tools.

Elm’s type system is very limited compared to other FP options out there. Not only are there no type classes, there is no higher kinded polymorphism either, so even dictionary passing style is out of the question. This means that polymorphism on properties like Functor are just not possible. Upgrading types is a pain, and code can rot compared to languages with these features. For example, lets say we realize we can eliminate error states in our code by upgrading part of it from using a standard List to using a ZipList. Making this move is going to require touching alot of code, simply because polymorphic abstractions like pure and fmap are not possible.

Elm has a bad attitude toward learning beyond a certain level. Ideas like Applicative are in the platform, but the term Applicative is no where to be found. Meaning people who wish to learn more are left in a lurch. Instead of serving as the half-way house to Haskell that it could be, Elm seems to deliberately be producing a thought prison. Where developers can get lured in and trapped in their progress. Some people are quite happy there, but others are left wondering where to go from here, once they have mastered everything Elm has to teach them.

The package manager limits portability. This is largely because Elm is so strongly hexagonal in it’s design. The package manger doesn’t support private instances, and actively prevents the publishing of code that binds to JavaScript. This is a real problem for real world use, as the Elm core team will leave gaps in functionality, sometimes for months on end. If you have the skill, you can close those gaps yourself, but wont be able to share your solution.

PureScript

PureScript is a purely functional language that is very inspired by Haskell. PureScript has many of Haskell’s features, and a fresh take that removes many of Haskell’s warts. Here are some reasons you might choose PureScript:

Here are some reasons you might avoid PureScript:

PureScript is reaching maturity extremely rapidly, and while performance is not yet fantastic, it’s more than enough for production use cases.

What’s in this bag?

PureScript is the medium maturity option, not as mature as Elm, but more mature than GHCjs. Tooling for PureScript is very nice for it’s age. Any gaps that are there now, should resolve with time, as this is a community that is strongly empowered. PureScript unlike Elm, is also general purpose, and extremely powerful. As a result, there is no one best way to go about UI development in PureScript. There are two fairly DOMinant approaches:

So how should you go about writing a UI in PureScript? It’s about what matters to you and what you care about. Should you go with Halogen you can expect a high degree of type level complexity and some very hard to reason about error messages. The abstractions in Halogen can be difficult to grasp, even for experienced functional programmers, but the pay off is awesome. You will gain incredibly reliable code, with few compromises.

Should you choose Pux, you will get much of the benefits of TEA, and some of the problems as well. Pux is designed to wrap React components, and those components are often of very poor quality. React is also fairly slow by itself, and so buying into Pux means buying into React as well.

Or you can roll your own framework, which is actually an attractive option here. You can mix and match, using parts of Pux and Halogen together with more fringe frameworks like Optic-UI. PureScript is fun and flexible, but there is also not a clear path to take, depending on your orientation, that is either a undesirable risk, or a highly desirable playground. Since there is a pure virtual DOM package in PureScript with great performance, I was able to roll my own version of TEA that blends TEA with Monadic programming in just a couple days. That would not have been possible in any other platform.

Programming model heterogeneity is actual worth considering. Today there is not a clear winner for how to program a purely functional UI, and working in a series of small burnable petri dishes might bring forth a better future for all.

Where is PureScript going?

Everywhere. PureScript is becoming more and more general and getting closer to competing with Haskell directly (though Phil (the author or PureScript) seems to disagree). So long as there is demand for Haskell like languages on the backend, there is room for a competitor that is strict by default, and designed to be embedded into existing platforms. With lessons learned from Haskell, PureScript boasts an ideal type class hierarchy, extensible records, and a documenting Eff Monad. With backends in C++ and JavaScript as well as the JVM and Erlang, PureScript is an incredibly ambitious project, but does it have a clear direction for UI development? No not really. Is that a problem? Maybe. Maybe not!

GHCjs

A paradox of maturity. GHCjs is the new old kid. GHC is obviously the most powerful, flexible, and mature functional programming system for production use-cases. And not by a little bit either, Haskell has over 20 years of brilliant contribution of offer, and an incredibly expansive, principled and well-maintained ecosystem of packages. But compiling to JS is fairly new, and immature. Here as some reasons to pick GHCjs:

Here are some reasons to avoid GHCjs:

Tooling

This is a paradox as well. On the one hand, you can compile GHCjs code with GHC. As a result you have access to the absolute best tooling out there for functional programming. You can use ghc-mod, and benefit from Haskell’s magnificent error messages. On the other hand, almost none of that tool chain works with GHCjs directly, so if you want GHC’s tools, you will need to switch compilers back and forth (not a big deal). GHCjs also lacks some high importance basics, like the ability to debug performance bottlenecks in the JavaScript runtime, or gain visibility into the execution path of code. That said, the company I work for now (Takt Inc.) has hired the author of the GHCjs compiler, and I am confident these issues will be addressed.

Reflex

GHCjs does have a clear path forward on browser UI development with it’s single popular library, Reflex. Reflex is Higher Order Reactive programming, and is highly Monadic. Buying into GHCjs today means you are going to be buying into Reflex. Reflex is highly composable and flexible, by mixing Monadic abstractions like crazy. This means developer discipline, as IO can be done anywhere in the code, and discrete state machines are common.

There is also no separation between stateful logic and view logic, as the work of building a system in Higher Order FRP is more about specifying a network, than making expressions about change. This can make code very natural to write, but harder to follow, as there is no enforced organization. On the one hand, Reflex code feels like solid FP in the Monadic programming model (complete with MTL), but on the other hand has an imperative quality where code is describing how we accomplish our goals, instead of what the solution is.

Reflex is not backed by virtual DOM, and talks about how it makes virtual DOM unnecessary. This is technically true, but it also means more manual documenting of interactions between the state of the application and the DOM, as keeping elements in place is not automatic.

If you value highly composable UI components, but don’t want the type level complexity of Halogen, then Reflex might be for you. Reflex offers high performance streams that work together. Push streams, pull streams, and a tuple of the two, which is essentially a Signal. The one downside of this approach, is that fewer things fit together, simply by dint of there being multiple stream types. Creating steams is also effectful in some cases, though why some cases need to return a Monad, and others are more pure, can be hard to understand.

Reflex is also very new, so the ecosystem of components is tiny. By far the smallest of all options discussed (thought that will change with time). The documentation and other gaps also exist today, almost entirely as a result of it’s young age. So plan on spending some time in the IRC channel for things that you might expect live in a nice polished guide.

Will Reflex be the only framework for web based UI development in Haskell? No obviously not. Competing abstractions will come along once GHCjs adoption is wider. But for the moment, GHCjs has it’s hero, and there is a clear path forward.

(Note: Reflex is not tied to GHCjs, but this article is about programming in the browser, and I felt that GHCjs is somewhat tied to Reflex. I am aware that react-flux is also an option, but don’t have the experience to comment. There are certainly other ways to do frontend development with GHCjs, but Reflex is the only one I’ve seen that appears to have significant traction).

Scala JS

I had not put Scala into the running of possibilities for purely functional programming, until @ValentinKasas corrected me. I’ve always been skeptical of Scala, ever since witnessing teams of people preaching FP while writing highly imperative code none the wiser. That said, there is clearly a ton of great FP being done in Scala, and with a little extra effort you can enforce purity.

Here are some reasons you might choose Scala JS:

Some reasons you might want to avoid Scala JS:

Should you choose Scala you are going to need to make some modifications, similar to JavaScript, before you will have a pure result. First ValentinKasas informs me that with WartRemover you can restrict the usage of mutable variables on other impurities. Leaving Scala with only unmanaged IO to contend with, which comes from libraries.

Libraries

IO can be managed in Scala by usage of the Task Monad in Scalaz, which is much like the IO Monad. Effectful actions can be wrapped in a Task, and it’s possible to localize the Task execution to a single location.

Alternatively you could write all your code in the context of Free Monads with Cats. And treat the interpreter of some command Functor as the entry point. This should also be possible with Scalaz, but is actually the canonical approach with Cats.

(Due note: I’ve written limited Scala and no Scala JS. ValentinKasas felt it deserved a mention, and he is right. Luckily I have just enough Scala experience to justify commenting.)

Conclusion

Everything has it’s place and a killer use-case. If you can use a purely functional language and avoid JavaScript, you should, but there is no clear winner.

Today I am doing Reflex, because thats the right fit for environment I am in, and loving it. That said, I’ve enjoyed Elm and PureScript immensely as well.

Its about fitting the situation in which you find yourself. There are no bad options here. It’s an incredible blessing, that we went from 0 viable ways to do FP in the browser to 4 in just a few years. All of these options are backed by corporate usage, so none of them are likely to die off any time soon!

I hope this helps you make a decision about which platform works best for you.

(Final note: If I forget to mention your favorite library, or felt I was mis-characterizing the subject matter in some way. Feel free to let me know! I’d love to learn more.)