Using Electron with Haskell

Versions used:

  • Electron 1.0.1
  • Stackage LTS 5.15
  • servant 0.4.4.7

If you want to grab the whole code from this post, it can be found at codetalkio/Haskell-Electron-app.

Not much literature exist on using Electron as a GUI tool for Haskell development, so I thought I’d explore the space a little. Being initially a bit clueless on how Electron would launch the Haskell web server, I was watching the Electron meetup talk by Mike Craig from Wagon HG (they use Electron with Haskell) and noticed they actually mention it on the slides:

* Statically compiled Haskell executable
* Shipped in Electron app bundle
* main.js in Electron spawns the subprocess
* Executable creates a localhost HTTP server
* JS talks to Haskell over AJAX

Importantly the bit main.js in Electron spawns the subprocess is the part that was somehow missing in my mental model of how this would be structured (my JavaScript experience mainly lies in webdev and not really with Node.js and server-side/desktop JS).

Riding on this epiphany, I decided to document my exploration, seeing as this is an area that is sorely lacking (GUI programming in Haskell in general). Before we start anything though, let me lay out what the project structure will look like:

Haskell-Electron-app/
  haskell-app/
    resources/
      ...
    ... electron files
  backend/
    stack.yaml
    backend.cabal
    ... servant files

Setting up Electron

Electron has a nice quick start guide, which helps you get going fairly, well, quick. For our purposes, the following will set up the initial app we will use throughout.

In your shell/terminal
$ cd Haskell-Electron-app
$ git clone https://github.com/electron/electron-quick-start haskell-app
$ cd haskell-app
$ npm install && npm start

And that’s it really. You’ve now got a basic Electron app running locally. The npm start command is what launches the app for you. But! Before doing anything more here, let’s take a look at the Haskell side.

Setting up the Haskell webserver

We’ll be using servant for a minimal application, but you could really use anything that will run a web server (such as Yesod, WAI, Snap, Happstack etc, you get the idea :).

Assuming that stack is installed, you can set up a new servant project with

In your shell/terminal
$ cd Haskell-Electron-app
$ stack new backend servant
$ cd backend
$ stack build

which will download the servant project template for you (from the stack templates repo) and build it.

To test that it works run stack exec backend-exe which will start the executable that stack build produced. You now have a web server running at 127.0.0.1:8080 - try and navigate to 127.0.0.1:8080/users and check it out! :)

For the lack of a better named I have called the application backend, but it could really be anything you fancy.

Contacting Servant/Haskell from Electron

For now, let us proceed with Electron and servant running separately, and later on explore how we can start the servant server from inside Electron.

Since the servant template project has given us the endpoint 127.0.0.1:8080/users from which it serves JSON, let’s set up Electron to call that and display the results.

By default the chromium developer tools are enabled in Electron. I suggest you keep them enabled while debugging, since that makes it a lot easier to see if anything went wrong. If you want to disable it, you just need to comment/remove a line in Haskell-Electron-app/haskell-app/main.js:

Haskell-Electron-app/haskell-app/main.js
...
function createWindow () {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600})

  // and load the index.html of the app.
  mainWindow.loadURL('file://' + __dirname + '/index.html')

  // Open the DevTools.
  // mainWindow.webContents.openDevTools() <-- this one here
  ...
}
...

Short interlude: since I’m a bit lazy let’s go ahead and download jQuery 2.2.3 minified and put that into Haskell-Electron-app/haskell-app/resources/jQuery-2.2.3.min.js so we can include it later on and get the nice AJAX functionality it provides.

Back to work, lets change the index.html page and prepare it for our list of users.

Haskell-Electron-app/haskell-app/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Heya Servant!</title>
  </head>
  <body>
    <h1>User list:</h1>
    <div id="status"><!-- The request status --></div>
    <div id="userList">
      <!-- We'll fill this with the user information -->
    </div>
  </body>
  <script>
    // Avoid clashing Node.js/Electron with jQuery as per
    // http://electron.atom.io/docs/v0.37.8/faq/electron-faq/
    window.nodeRequire = require;
    delete window.require;
    delete window.exports;
    delete window.module;
    // Fetch jQuery
    window.$ = window.jQuery = nodeRequire('./resources/jQuery-2.2.3.min.js')
    // The JS file that will do the heavy lifting
    nodeRequire('./renderer.js')
  </script>
</html>

And finally we’ll implement the logic in renderer.js.

Haskell-Electron-app/haskell-app/renderer.js
// Backend and endpoint details
const host     = 'http://127.0.0.1:8080'
const endpoint = '/users'
// Retry configuration
let maxNoOfAttempts        = 50,
    waitTimeBetweenAttempt = 250

let _fetchUserList = function(waitTime, maxAttempts, currentAttemptNo) {
  $.getJSON(host + endpoint, function(users) {
    $('#status').html(`Fetched the content after attemt no.
                       ${currentAttemptNo}!`)
    // Construct the user list HTML output
    let output = "";
    for (let i in users) {
      let user = users[i]
      output += `ID: ${user.userId},
                 Firstname: ${user.userFirstName},
                 Lastname: ${user.userLastName}
                 <br>`
    }
    $('#userList').html(output)
  }).fail(function() {
    $('#status').html(`Attempt no. <b>${currentAttemptNo}</b>. Are you sure the
                       server is running on <b>${host}</b>, and the endpoint
                       <b>${endpoint}</b> is correct?`)
    // Keep trying until we get an answer or reach the maximum number of retries
    if (currentAttemptNo < maxAttempts) {
      setTimeout(function() {
        _fetchUserList(waitTime, maxAttempts, currentAttemptNo+1)
      }, waitTime)
    }
  })
}

// Convenience function for _fetchUserList
let fetchUserList = function(waitTimeBetweenAttempt, maxNoOfAttempts) {
  _fetchUserList(waitTimeBetweenAttempt, maxNoOfAttempts, 1)
}

// Start trying to fetch the user list
fetchUserList(waitTimeBetweenAttempt, maxNoOfAttempts)

We simply request the JSON data at http://127.0.0.1:8080/users, with $.getJSON(...), and display it if we received the data. If the request failed, we keep retrying until we either get a response or reach the maximum number of attempts (here set to 50 via maxNoOfAttempts).

The real purpose behind the retry will become apparent later on, when we might need to wait for the server to become available. Normally you will use a status endpoint that you are 100% sure is correct and not failing to check for the availability (inspired by the answer Mike from Wagon HQ gave here).

Launching the Haskell web server from Electron

Now to the interesting part, let’s try to launch the Haskell web server from inside of Electron.

First though, let us set the haskell-app/resources folder as the target for our binary, in the stack.yaml file, with the local-bin-path configuration value.

Haskell-Electron-app/backend/stack.yaml
resolver: lts-5.15
local-bin-path: ../haskell-app/resources
...

Now let’s compile the executable.

In your shell/terminal
$ cd Haskell-Electron-app/backend
$ stack build --copy-bins

The --copy-bins (or alternatively you can just do stack install) will copy over the binary to Haskell-Electron-app/haskell-app/resources as we specified (it defaults to ~/.local/bin if local-bin-path is not set).

After that, it is surprisingly easy to launch the executable from within Electron (well, easy once you already know how). We will change main.js to spawn a process for the web server upon app initialization (i.e. the ready state). Since there are bits and pieces that are added I’ll include the whole file, with most of the comments removed.

Haskell-Electron-app/haskell-app/main.js
const electron = require('electron')
// Used to spawn processes
const child_process = require('child_process')
const app = electron.app
const BrowserWindow = electron.BrowserWindow

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
// Do the same for the backend web server
let backendServer

function createWindow () {
  mainWindow = new BrowserWindow({width: 800, height: 600})
  mainWindow.loadURL('file://' + __dirname + '/index.html')
  mainWindow.webContents.openDevTools()
  mainWindow.on('closed', function () {
    mainWindow = null
  })
}

function createBackendServer () {
  backendServer = child_process.spawn('./resources/backend-exe')
}

app.on('ready', createWindow)

// Start the backend web server when Electron has finished initializing
app.on('ready', createBackendServer)

// Close the server when the application is shut down
app.on('will-quit', function() {
  backendServer.kill()
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', function () {
  if (mainWindow === null) {
    createWindow()
  }
})

We are using the child_process.spawn command to launch our backend web server. Let’s briefly go through what is happening:

  • Imported the child_process module with const child_process = require('child_process')
  • Defined a variable let backendServer that’ll let us keep the backend server from being garbage collected
  • Added a function createBackendServer that runs child_process.spawn('./resources/backend-exe') to spawn the process
  • Added the createBackendServer function to the ready hook with app.on('ready', createBackendServer)
    • Close the backendServer when the event will-quit occurs

And voila! We now have Electron spawning a process that runs a Haskell web server! :)

Next step would be to package the app up for distribution to see if that affects anything, but I’ll save that for another time (and Electron already has a page on distribution here).

Compiling SCSS and JavaScript in Hakyll

Versions used:

  • Hakyll 4.8.3.0
  • hjsmin 0.2.0.1
  • Sass 3.4.18

This seems to be an often asked question, so I thought I’d try and share the approach that I’ve arrived at after having explored a couple of solutions to the problem. If you want to see the full code in action, check out the repo for the codetalk.io site (linking to v1.0.0 is intended, in case the code changes later on).

Compiling and minifying JavaScript

For some reason Hakyll does not include its own JavaScript compiler, which makes little sense. Luckily there is a package called hjsmin giving us Text.Jasmine, which we will use to both compile and minify our JavaScript files.

NOTE: From personal experience an earlier version of hjsmin (0.1.5.3) would throw a parse error on some files (like jQuery). This has later been fixed in 0.2.0.1, but unfortunately Stackage is using 0.1.5.3 in the current LTS 5.15.

To get hjsmin 0.2.0.1 working with stack, you can add the following to the stack.yaml file in the project.

stack.yaml
...
extra-deps: [ hjsmin-0.2.0.1
            , language-javascript-0.6.0.4
            ]
...

and hjsmin == 0.2.* as a dependency in the projects cabal file.

Now we are ready to construct the compiler itself.

site.hs
import qualified Data.ByteString.Lazy.Char8 as C
import           Text.Jasmine

-- | Create a JavaScript compiler that minifies the content
compressJsCompiler :: Compiler (Item String)
compressJsCompiler = do
  let minifyJS = C.unpack . minify . C.pack . itemBody
  s <- getResourceString
  return $ itemSetBody (minifyJS s) s

The code is fairly straightforward. We use the Text.Jasmine provided function minify which has the signature ByteString -> ByteString, meaning it takes in the JavaScript code as string, and produces the result to a string.

Later on inside the main Hakyll function, we use it simply as we would the other compilers.

site.hs
-- | Define the rules for the site/hakyll compiler
main :: IO ()
main = hakyll $ do
  -- | Route for all JavaScript files found in the 'js' directory
  match "js/*" $ do
    route   idRoute
    compile compressJsCompiler
  -- The rest of your rules...

Compiling and minifying SCSS (Sass)

For those who aren’t aware, there are other ways to write CSS than CSS. Sass and SCSS adds a lot of niceties to CSS, such as nesting, variables and mixins, and compiles to normal CSS. You can read more about that on their website.

This time we rely on external dependencies, namely the sass tool, which can be installed with gem install sass.

NOTE: There is a library called hsass, which provides a Haskell interface, but I’ve been running into problems with linking to the underlying C API. As such, I’ve opted for the external dependency for now.
site.hs
-- | Create a SCSS compiler that transpiles the SCSS to CSS and
-- minifies it (relying on the external 'sass' tool)
compressScssCompiler :: Compiler (Item String)
compressScssCompiler = do
  fmap (fmap compressCss) $
    getResourceString
    >>= withItemBody (unixFilter "sass" [ "-s"
                                        , "--scss"
                                        , "--compass"
                                        , "--style", "compressed"
                                        , "--load-path", "scss"
                                        ])

This time we have no library dependencies, and use the Hakyll provided function unixFilter to call the sass tool. An important thing is the arguments that we pass, which I’ll explain briefly:

  • -s tells sass to take its input from stdin
  • --scss tells sass to use the SCSS format
  • --compass tells sass to make compass imports available
  • --style compressed tells sass to compress the output
  • --load-path scss tells sass to look for modules in the scss directory (if we import stuff)

Much like with the JavaScript compiler, we use it in the main Hakyll function as such:

site.hs
-- | Define the rules for the site/hakyll compiler
main :: IO ()
main = hakyll $ do
  -- | Compile the SCSS, from 'scss/app.scss', to CSS and serve it as 'app.css'
  match "scss/app.scss" $ do
   route   $ constRoute "app.css"
   compile compressScssCompiler
  -- The rest of your rules...

You can now happily compile both JavaScript and SCSS in your Hakyll project, without much hassle :).

Briefly on the purpose of Functors, Applicatives and Monads

In a recent thread on /r/haskell about how to motivate the AMP proposal in teaching, I read a comment that finally helped me understand the purpose of Functors, Applicatives and Monads.

Wait, what is AMP?

For the reader that hasn’t followed this debacle, the AMP proposal is basically about making Applicative a superclass of Monad. That this hasn’t been so is often considered one of the historical errors Haskell carries around, since a Monad is always an Applicative.

The hierachy of Functor, Applicative and Monad is thus changed to,

Typeclass hierachy
class Functor f where
    -- ...

class Functor f => Applicative f where
    -- ...

class Applicative m => Monad m where
    -- ...

which makes more sense than the arbitrary split before (i.e. Monad was just “on its own”).

The concerns surrounding this change has mostly been about beginner-friendliness, since a Monad now needs an instance of both Applicative and Functor.

There are various other considerations, which the interested reader can find out more about on the GHC 7.10 migration page or the Haskell wiki entry about AMP.

Back to Functor, Applicative and Monad

I’m mostly going to paraphrase /u/ForTheFunctionGod’s comment (which you can find here), while trying to expand a bit on it with my own understanding.

One can naturally extend the intuition of a Functor to an Applicative and from there to a Monad.

An explanation of the first two might go as follows:

Let’s say I have a list of numbers and want to add 2 to each of these numbers. You can write fmap (+2) [1..10] (using Functor).

But what if I had two lists of numbers and wanted to add each number from the first list to each number from the second list? With Applicative you can do just that - it’s like fmap only it can take multiple arguments. You can write (+) <$> [1,5,10] <*> [11..13].

From here, one can motivate Monad by asking:

What if I wanted to abort midway through - say, if I encountered a problem? You can then write:
add xs ys = do
    x <- xs
    if x < 0 then []
    else do
        y <- ys
        if y < 0 then []
        else return (x+y)

Thus we have the natural hierachy:

  1. Functor: apply a function to a container.
  2. Applicative: apply a multi-argument function to multiple containers.
  3. Monad: like Applicative but I can decide what to do next after each step.

Examples of Functor, Applicative and Monad

While the above may help a bit in the “why?”, there is still the question of how to use them. I won’t go into much detail since there exists a wealth of information on this topic already (you can quickly google them), but just give some brief examples of what happens when using them.

Functor is the simplest, and can be thought of as a much more general map. Wherever you use map you can always replace it with fmap, but not the other way around.

> fmap (*2) [1..10]
[2,4,6,8,10,12,14,16,18,20]

fmap simply takes every element of the list and applies the function to it.

Applicative on the other hand is a bit more tricky to understand. The best starting point is probably to show where fmap is not enough.

Imagine we want to apply the function * (multiplication) to a list, we could try fmap (*) [1..3] but that would give us a bunch of partially applied functions back, like [(*1), (*2), (*3)].

Now, what can we do with this? We can for example map a value onto the list of partially applied functions, which would look something like this using fmap,

> let a = fmap (*) [1..3]
> fmap (\f -> f 9) a
[9,18,27]

which can be seen as applying a function that takes a function as argument an applies 9 to that function, to every function in the list a. The real problem comes when we want to apply a Functor function to Functor values, but I won’t get into much detail on that (you can read more here).

Luckily, instead of writing the above, we can use <*> which is a part of Applicative. We can instead write,

> fmap (*) [1..3] <*> [9]
[9,18,27]

Note that the reason we put 9 inside a context (here a list), is because Applicative expects everything to be a Functor. Since the first part is so common, Control.Applicative actually exports <$>, so we can do the following instead, replacing fmap,

> (*) <$> [1..3] <*> [9]
[9,18,27]

Admittedly, it’s a bit more interesting when we want to apply the multiple functions to multiple arguments, as such,

> (*) <$> [1,5,10] <*> [11..13]
[11,12,13,55,60,65,110,120,130]
> (*) <$> Just 3 <*> Just 5

or perhaps inside contexts, such as Maybe,

> (*) <$> Just 3 <*> Just 5
Just 15

but I won’t go into more detail about that, since that isn’t the purpose of this post.

Monad is the last, but perhaps most tricky. I’ll try to be as brief as possible though. Too see the “what happens next, based on what happened before” part, we will look into the Maybe monad used with do notation. It will not explain Monad usage in general, but should be enough to get the gist that “something” happens between the steps.

For example (note that I use ; instead of linebreaks because we are executing in the GHCi REPL),

> do Just 6; Nothing; Just 9
Nothing

You may be aware that a do block returns the last line executed in it - so why did it here return Nothing when the last line was Just 9? That is because on each step the bind function >>= (or =<< for the other direction) is applied. The Maybe Monad defines that if it encounters a Nothing then every subsequent actions also return a Nothing.

To understand this better, let’s take a look at the Maybe instance,

Maybe Monad typeclass instance
instance Monad Maybe where  
    return x = Just x  
    Nothing >>= f = Nothing  
    Just x >>= f  = f x  
    fail _ = Nothing  

Notably here is the line that says Nothing >>= f = Nothing. It throws away whatever future action it gets and just returns Nothing. This is in contrast to Just x >>= f = f x which can be seen as unpacking x from Just x and then applying the future action, f, to that x.

Wrap up

Hopefully this should have helped understand the difference of Functor, Applicative and Monad a bit, and how they work together. To understand these concepts more in depth, I recommend reading a bit up on them, and especially for Monad, try reading about the Writer and State Monads, how they are used and how they are implemented. One resource for that is the chapter on a few more monads in LYAH.

If you are confused about “What exactly is a Functor, is it a class or interface or whatever?”, then I gave my take on it here. Other than that, I encourage that you read a bit on Type Class’.

Finally, I can deeply recommend the book Haskell Programming - from first principle. It is still in development as of this date, but already features 700 pages of quality content. Simply the best Haskell book I have ever read (or, I’m still reading it as of writing).