Software is hard
Software is hard

Using Web Streams with PureScript

7 minutes read

This is an updated version of the original article. 
I want to thank @joseanpg for the corrections and the idea regarding TextEncoder.

Usually, my articles are mostly too long, filled with too many screenshots and philosophy. But this time I’ll try to remain concise and, hopefully, more precise than usual. I’ll refer to Jake Archibald’s article on Web Streams and his prediction that they’ll become the dominant web technology of the Year 2016 (in combination with Service Workers, of course). The article is filled with lots and lots of nice code examples you can basically copy & paste into your browser (some of them need Chrome Canary, though), so I thought it would be a good exercise for me if I try to write a Web Streams-demo with PureScript + some unavoidable foreign imports. As you may already know, PureScript is a Haskell-like language that transpiles to JavaScript and doesn’t need any kind of additional runtime. Just write your code in pure Haskell and it’ll later be converted into a very readable JavaScript. Well, surely a much more readable JS than my own which I’m not showing it here, because I don’t want to lose my last few readers. If you want to know more about PureScript then I’d recommend you to visit these few nice pages.

The sources of the demo used in this article are located here.

Now, what are web streams? Simply spoken: web streams are a technology that allows browsers to gradually consume incoming data. Imagine, you’re receiving some video-streams or high-density images. You could either try to download everything at once that can quickly eat up your resources, or gradually, over time download small chunks to process them as they arrive. Our browsers use streaming techniques by default but now there’s a standardization process that exposes these APIs to JavaScript.

Foreign Imports

Our demo app will comprise of two parts: a library that maps the side-effect free world of PureScript to the effecful realm of JavaScript and a simple app that shows the incoming stream in your browser’s console. While reading Jake’s article I spotted a few important names and terms, like WebStream, Response, Body, Reader, ReadableStream etc. Some of them are important for this demo, others not yet. And because I didn’t want to force you to install Chrome Canary (or Chrome at all) I’l describe a demo that only plays with the first example from Jake’s article: Streams + the Fetch API. And the first step in our journey will be the definition of foreign imports that glues the worlds of JS and PS together.

This is how our foreign imports from API.Web.Streams.purs look like:

foreign_imports

 

We define a few important effects and values like WebStreamM, Response, Reader etc. Not all of them will be used but just for the case we expand our demo later. For now we’ll only see WebStreamM running, as this is our base “Effect”. In Haskell lingo: its kind (a metatype) is called ‘effect’ and it describes an effectful computation, like reading from a stream. Everything that’s not pure in Haskell has to be confined to some container or context. In the above case we have to isolate our unpure computation from the rest of the code that is pure, in PureScript’s definition of purity. This also means that all values generated within an unpure context can never leave it or be intermingled with the pure ones in any way. We can’t do this in PureScript, never! If you ask yourself why the name of the ‘context’ WebStreamEff ends with an Eff the answer is rather simple: it’s a Monad which wraps our effectful computations. In this case its the handling of web streams which are, of course, side-effects and everything else but not pure. So, they require a proper Monad to control them. No, please, do not leave my blog! I’m not going to teach you anything about monads here. I did it once and I deeply regret it.

Just see a monad as a context where we hold our dynamically generated values, that is: value out of our direct control. Below we have a few additional foreign imports that declare functions to be used in our demo app. Especially the read function that takes a String (our stream’s URL), a callback of type ProcessResultCallback and a TextDecoder. Just like in JavaScript functions are first-class-citizens in PureScript and can be treated like any other value, used as an argument etc. Well, they are values because they expand to values when we execute them. The “return value” of read is an effectful computation which is defined above as WebStreamEff.

Additionally there’s a function for creating new TextDecorders with different encodings as defined in this list from MDN.

For even more control of the stream flow and the decoding procedure we can use the decode function that either accepts a valid TextDecoder or creates a new one if we pass Nothing as argument.

There’s a little bit of boilerplate-code describing WebStreamEff that says nothing else but: for all values of type ‘a‘ we define a function that will use following effects: CONSOLE, any optional effects ‘e‘ and will return a value of type ‘a‘. Here I used a few times the word ‘return‘ but this was just to describe things the simple way: actually, there’s no PureScript-return like the return in JavaScript. The last expression in a PureScript function is its final result. This is the real`return value`.

Writing some JavaScript boilerplate

The above foreign imports may look nice but they’re pretty useless without some background JavaScript boilerplate code. To achieve this task I’ve stolen used Jake’s original code and created PureScript-acceptable functions. When I say “PureScript-acceptable” I mean its special behaviors and expectations when it comes to functions. For example, PureScript doesn’t know anything about multi-argument functions. In PureScript’s world every function is curried. So I had to redefine the whole mechanism and write additional nested functions each consuming only one argument from the original function. The two most important things to watch out are the additional returns before function definitions and the seemingly superfluous () after their calls. Well, they’re not superfluous because when calling a multitude of nested functions we have to take care to execute them all before sending the final value back to PureScript. Also, returning each of the functions leads to the same effect as executing a single function with multiple arguments. These two techniques allow PureScript to work directly with JavaScript and vice-versa.

boilerplate_code_foreign_import

In this code part we define a small mechanism that will read from stream and forward this data to the callback function from PureScript. Line 51 shows how we have to call this PureScript-function. The `returned` Promise from the original Reader (Line 41) has to be called inside an additional wrapper (Line 58) that will take care of it being correctly resolved while our nested callback at Line 51 continues to process data. Our TextReader, that we either pass as a non-null argument or immediately create at Line 34, is continuously consuming the incoming chunks at Line 49.

Of course, all this probably looks too convoluted and surely there could be a more elegant solution than mine, but that’s it for now.

This is the code of our Test App:

test_app

We define a (not very intelligent) Callback, instantiate a TextDecoder for UTF-8 and call the foreign import read to handle the incoming Web Stream from the given URL.

And this is how it looks like:

 

Conclusion

Well, there’s no need to advertise it more. Go and read Jake’s article! It’s an exciting technology and I can only hope that my examples from above aren’t doing any disservice to the PureScript community. When it comes to PureScript and/or Haskell I’m still a noob trying to learn something new each day (and night).  :mrgreen:

Have fun!

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.