30 minutes read
I use RactiveJS not only because it’s fun to develop web apps in my spare time but also in my daily work at advarics GmbH. All of our internal and consumer web apps are built with RactiveJS and therefore I’ll allow myself to say that everything I say about RactiveJS is based on some real-world experience. One thing is to play around with some nice toy but something totally different when you do your daily business with it and the customer satisfaction depends on it.
And this is the point where PureScript comes in.
Haskell, mighty Haskell.
I’m by no means an experienced PureScript developer. I’m just a beginner and trying to learn it from two different perspectives: as a functional-languages enthusiast and as an employee trying to find the best solutions for real-world problems. And one of the biggest problems most developers facing today is the management of state and side-effects. In our interconnected world that knows no rest and offers no pause-button the search for an ideal state-management solution transcends into the Quest for a Holy Grail. Knowing your own state and the inevitable side-effects is already complex enough but controlling all states and their side-effects around you is actually impossible. At least as long as you’re running after them and trying to control them all at the same time. Haskell, this legendary, partly loved partly derided language offers a completely different approach: open hostility to all kinds of side-effects and uncontrolled state. Haskell is the bouncer among the programming languages. And PureScript is based on its DNA. And in combination with Ractive’s powerful Parallel DOM, which handles the DOM-transformations in a functional way, the two truly become a Dynamic Duo for real-world Web Development. On one side there’s a purely functional language with real types and no uncontrolled state, and on the other a low-footprint UI-library for manipulating the HTML DOM without manually trashing it. Of course, this is only one of many possible solutions and PureScript offers bindings for other libraries like React or virtual-dom (Halogen). You may completely disagree with me. Now let me describe the demo app that I’ll be using to describe some important parts of PureScript and RactiveJS.
Building a WebApp with PureScript
A PureScript source file describes a module comprising of several parts (not all of them are mandatory): module definition, imports, type aliases, instance commands (not to be confused with OO-instances) etc. At the first sight the syntax may feel very alien and just don’t expect yourself to “grok” PureScript after a few trials. A really productive strategy would be to read the fantastic book written by PureScript’s creator, Phil Freeman.
You can get it for free but I strongly recommend you to buy it. The money goes to a charity that organizes code-learning clubs for kids. So, this is a reason enough to spend a few $ or €, isn’t it?
And this is how a PureScript code file may look like. In fact, this is the app.purs file from the demo app. I deliberately removed the comments. They’re available in the GitHub version, of course.
Modules, so many modules
It may feel awkward at the beginning but the structure of a typical PureScript source file is actually very clean and much better organized than most of the JS-scripts: Here we see the beginning of the module indicated by the keyword module, followed by its name. The name itself can be separated with dots which indicate namespaces. Only the last name counts as a module name. After the module name you often write several imports which include some standard modules like Prelude or Control.Monad.Eff (that helps you working with the dreaded state and “effects” caused by Console, DOM-manipulation, random-number generation and so on). Often you’ll find yourself importing many different modules and therefore you should consider using packages like purescript-batteries that do this job for you. I did not use it in this demo app because, in my opinion, a demo should not use the latest and greatest of the available tools. A demo has to be simple and pure. However, when developing complex apps you should definitely use something like purescript-batteries. In general, when searching for proven solutions of standard problems consider visiting Pursuit, the documentations & package database for PureScript.
A module in PureScript is basically a source file optionally exporting some functionalities and/or types. For example the Prelude module in the example above exports many different things but we’re interested only in Unit, bind and not. This is why we put them in parentheses after the module name. We indicate that we’re only interested in these three exports. Everything else remains invisible to us. If we’d like to import everything at once we could just use (..). Also we could simply ignore it completely but the compiler would complain and shows us a warning like this:
We see that the compiler correctly inferred only three imports and rightfully warns us. This is one of the best features of PureScript that not only helps when dealing with imports but also with function signatures. This is, for example, what happens when I remove the signature from the setRandom function (line 15 in the picture above).
The compiler infers the correct signature for us so we can simply copy/paste it.
Functions are everywhere
I’d rather avoid theoretical discussions about functional programming and which language is more or less functional. Let’s be pragmatists: functional programming is about managing state and side-effects (some even distinguish between side-causes and side-effects). PureScript is a functional language because it’s managing state and side-effects so perfectly that there are no side-effects allowed at all (more precisely: no uncontrolled side-effects). The usability of such programs is questionable because we need some kind of state and state-change is inevitable. So, how does PureScript “allow” state and side-effects without losing its functional street credibility? Enter Monads. 🙂
No, I don’t think that it’s the right time to describe monads in fully. Actually, I tried it once by using Scala and now I think I failed completely. OK, I provided some Scala code and a few nice pictures,…but I couldn’t properly describe Monads! However, this isn’t because they’re “hard to understand” but because they’re “hard to explain”. For the purposes of this articles, just imagine Monads were some kind of Callback-Functions with some extra powers. Monads are Callbacks. And because Haskell hates side-effects and PureScript is Haskell reinvented, it’s obvious that it shares the same hate towards side-effects (and any kind of “free riding” state). Well, what should be done when you try to control some state? Just use a Callback when you’re done and PureScript will continue its work, just as if there were neither a state nor any side-effects. Um, did I say “Callback”? Actually, I wanted to say “Monad”. Yes, just use a Monad if you’d like to keep your friendship with the PureScript compiler intact. PureScript will never, ever, deal with state-changes and its side-effects. For such dirty stuff you have to use Monads and also to properly declare them. In the function signature, of course. One by one. No Monad left unregistered! try to “forget” a Monad and PureScript will catch you immediately (remember the automatic function signature inference from above?). You can also use an alternative way of setting a special expression that automatically expands to any kind of side-effect that deserves a Monad. The real power of PureScript lies in its extreme expressivity. Let’s look at the setRandom signature and try to spot a monad:
“For all elements of function X blah, blah, blah“. It’s really like simple high-school math. Now, let’s try to read the signature:
setRandom is defined as a function for all elements of type e. -> Ractive -> Eff (random :: RANDOM, ractiveM :: RactiveM | e) Unit. … Ok, we now understand something from the beginning of the signature but not the rest of it. What does this “arrow” mean? Why does PureScript use parentheses in such a weird way?
First, the “arrow” indicates a very important aspect of PureScript that deals with a (presumably) less known fact that PureScript only has functions with one parameter. There are no multi-parameter functions in PureScript! Even if you say, for example: I pass to function X three arguments of type Number, String, Number, PureScript would still call three functions, each with one of the given arguments. We also say: PureScript functions are curried. Just imagine, PureScript would always conflate the functions to just one parameter. And the aforementioned “arrow” symbol indicates (and separates) function parameters from each other. Additionally, and more importantly, it shows us the input and return values of each of the functions. This means, for example, that setRandom‘s first application with the input value of type Ractive returns an Eff-Monad which then becomes a new input of the second application. It’s basically a chain reaction continuously forwarding values from the last application to the next one.
So, let’s try to read the function signature again:
setRandom is defined as a function for all elements of type e. that’s being applied to its argument of type Ractive, which returns a resulting function that’s being applied to its argument of type Eff
We have used the verb “read” two times because we have to consume two arguments by applying two functions. The first one is Ractive (well, it has something to do with the RactiveJS, that’s for sure) and the second one is Eff (Did you spot a Monad?).
We also say “to apply” and not “to call” a function. In PureScript we do not call them, we apply them. Why? Because a function in PureScript is based on math and a function in mathematical sense is an association between the elements of two sets. Here’s a simple example of the first three Latin alphabet letters mapping to their ordinal numbers.
Let us now reiterate the complete application of the setRandom function:
We have two applications, one with the Ractive-argument, another with Eff-argument, but what’s the meaning of parentheses after Eff? Another function application? No, absolutely not. Brackets in PureScript have a completely different meaning and because all functions contain only one argument there’s simply no reason to use parentheses as mandatory parts of any function signature. In this example the parentheses describe the structure of an argument of type Eff. Let’s concentrate on Eff only, just to learn a few facts which apply to PureScript structures in general.
Eff (random :: RANDOM, ractiveM :: RactiveM | e)
First, Eff is the name of the abstraction for dealing with side-effects. It’s an integral part of PureScript’s ecosystem. Just look again at the import list and you’ll spot several Eff‘s. For Haskell users among you, the Eff is PureScript’s IO-Monad. For readers without functional background: Eff is the abstraction in PureScript for describing side-effects. A side-effect is anything that changes a state, like a random number generation, DOM manipulation, writing to the console, to printers, to networks etc. Anything that manipulates some kind of state. And because there are many different changes that can happen, PureScript expect us to describe what kinds of “effects” could happen during a certain function application. This is the reason why we use Eff and write all these names inside parentheses. The parentheses describe the different “effects” that can or will happen during the function application. Ultimately, by naming the different effects inside the parentheses we define the structure of our monadic function argument that is based on the type Eff. We see RANDOM and RactiveM, and also we see some weird combination of pipe character | and e. These three elements are fields of the structure under Eff. A simple analogy to OO-programming would be “class properties”.
Well, the name RANDOM is familiar to us, it must be some kind of random number generation, After all, our function is named setRandom, so this shouldn’t be a big secret to us. But what about RactiveM and this pipe-and-e combination. RactiveM is just another Monad describing a separate effect defined in the purescript-ractive library. This means that we can also define our own Eff-Monads and are not confined to standard ones (like console, random, DOM etc.). If we want to indicate some special state changes, like manipulating RactiveJS-instances for example, we just define our own monads. This is how it looks like for RactiveM from Ractive.purs (in src/Control/Monad/Eff):
And what about the lower-case e? The e is actually a template that signals the possibility of embedding further effects not explicitly defined inside the parentheses. This means that we can apply setRandom to some additional effectful changes and the function would integrate them automatically as elements defined by the lower-case e. The e acts as a placeholder for all not directly named effectful changes. It helps us to specialize our function for new effect types. From the opposite perspective this also means that we can be more rigid by simply removing the e from the “parameter list” and block any possibility of accepting any additional effectful changes we didn’t declare in advance. Now, let’s try to read the function signature again:
setRandom is defined as a function for all possible effects of type e which is applied first to the argument of type Ractive, then to the argument Eff whose structure comprises of effects of type RANDOM and RactiveM and can be specialized to additional effects of type e.
In the PureScript Book you’ll find a much better explanation of the forall-keyword and the parameter e. It’s called a quantified or polymorphic type which isn’t specialized in advance. This doesn’t mean that PureScript supports duck-typing like Ruby or Python because the types are always known at runtime. Some also call it “static duck typing” because we can substitute different types for symbols like e.
There’s one more thing I deliberately left out: the Unit after the closing bracket of Eff. This is the return type of the Eff-Monad. Usually, a function’s return value is indicated by the type after the last arrow in a PureScript function (look at the definition of the function inverse, for example, that returns a Boolean). But in this case there’s no arrow behind the monad Eff. This is not some weird exception but a standard behavior. Remember, Monads are Callbacks, so you don’t get any return value directly from a callback but only after it’s completed its tasks. Therefore, the return value of setRandom is a structure of type Unit which can be interpreted as “void” in C#, Java etc. Practically, there’s no return value at all. So, a complete description from above should end with: and returns a value of type Unit.
- it’s only a library and not a ‘framework’
- it’s declarative by default
- it doesn’t expect you to manipulate the DOM by hand
- it offers syntactic sugars like proxy events
- it adapts very nicely to other libraries via adaptors
- and last but not least, it get’s completely out of your way.
We see some already known elements like foreign import, forall etc. We also see that the second parameter contains only parentheses but nothing in front of them. So, this isn’t a monad like Eff in the previous example. Also, there are two additional arrows indicating and the signature clearly states that the function returns a value directly. But this time it comprises of two parts: RactiveEff and Cancellable. Are these two types returned at the same time? Maybe subsequently? No, it’s not that simple. This return value is a type alias defined at the beginning of the Ractive.purs file:
RactiveEff is an Eff-Monad capable of interleaving the effects of type e together with the predefined RactiveM effect and it returns a value of the given type a. Ultimately, the RactiveEff type alias is a type constructor that creates a Monad of type Eff which returns a value of type Cancellable. The Cancellable value is based on the result returned from RactiveJS which is capable of cancelling the registered event handlers. The kind of Cancellable type is defined here:
The star symbol signals the kind “value”. We now know that there are two kinds named “effect” and “value”. But there’s more in PureScript world. We also have kinds like * -> * that describe functions that map from types to types (hence the “arrow” between the stars). A typical example is the List type constructor. To test its behavior we should use PureScript’s REPL (read-evaluate-print-loop) called PSCi.
I’m using here the standard tool for building and managing PureScript packages called pulp to invoke the REPL and you should consider using it too. The option “-m” stands for multi-line so I can insert several lines before letting the code being executed via CTRL+D. Then I import the module defining the type Data.List.List and display its kind via command :k (or in longer form :kind). Here we see that PureScript’s List is a type constructor, a function from types (*) to (->) types (*). In fact we can’t create lists of elements like in other languages. There’s no way to do something like Mylist = new List(1,2,3,4,5). Instead we first have to create a new type constructor, for example List a, capable of constructing Lists that contain elements of type a and then apply it to each of the future elements of this list. Remember, in PureScript we never call a function but only apply it. So, the same rule “applies” when constructing compound structures like Lists. Simpy spoken: PureScript has an additional layer of indirection between the “ordinary” constructors, similar to those in C# or Java, that create instances of types and type constructors that create those constructors, like List a.
onLogoClicked and onControlButtonClicked are structurally almost the same. The first one expects just one more effect, RANDOM, but the rest of the signature is interchangeable. We see that both function applications manipulate our Ractive instance and therefore make the usage of the RactiveM-Monad mandatory. Because we now know how to read function signatures their internal behavior is not a big secret anymore. On each click on the PureScript-Logo in the demo app a new radom number will be generated by applying the function we already know very well: setRandom. There are some additional constructs we surely know from other languages: if…then…else. Our little app can decide if the random number should be generated or not. If not then a message is made visible in the text field (“message”) of the Ractive instance. To set this message in the Ractive instance (that is: to change its internal state) another function is needed: change
Here’s a small video showing the demo in action.
PureScript is wonderful and wonderfully simple. The same applies to RactiveJS. It’s small and simple and doesn’t try to teach the “right way” to code your web apps. Both of them are not complex. What’s complex are our own habits. Habits that lead to spaghetti code, hidden state-changes, mutable-by-default values, convoluted dependencies etc. It’s also our own laziness when it comes to find the best solutions. Often the “best” solutions are just cheapest or quickest ones that sooner or later reveal their not-so-cheap nature. We, developers, deserve better environments. And our customers deserve better products that can improve and extend over time without incurring technological debt or sudden interruptions and crashes. This article is by no means a proper introduction to PureScript and/or RactiveJS. It’s just a small example on what can be achieved when we combine declarative UI-libraries with purely functional languages.