Seamless docker

Today I learned docker container can act as if it was an application running on the host system with little effort

# Build docker image
$ docker build -t seamless-docker .

# Run a process in an isolated container
$ docker run --net host --volume `pwd`/shared:/shared seamless-docker

Let's break it down:

--net host exposes host network to the container helping the program running in the container act as if it was running in the host system.

--volume `pwd`/shared:/shared creates a shared volume between the host system and the container. It supports writes unless we add :ro  at the end. Any time modification is made in the host system or the container, the other will get the update.

Note:

By default Docker Desktop on MacOS does not allow creating a shared volume unless the directory is whitelisted. Some directories are whitelisted by default. File Sharing preferences can be changed in Docker -> Preferences -> File Sharing

Integrate PureScript with your JavaScript application

PureScript is a strongly-typed functional programming language that compiles to JavaScript. It means we can benefit from the type safety not only in new, but also existing applications.

PureScript has a top-notch FFI (Foreign-Function Interface) allowing us to call JavaScript functions from within PureScript. Not only this but we can also use PureScript modules from JavaScript.

Installing PureScript

First, we need to install global dependencies — the PureScript compiler, the package manager and the build tool: yarn global add [email protected] psc-package pulp.

Generating project structure

Pulp, the build tool, allows us to generate a basic project structure by running a single command: pulp --psc-package init.

It will create the src and test directories as well as psc-package.json containing a list of dependencies. Once created, pulp will install PureScript packages to .psc-package directory.

You can now compile and run src/Main.purs by typing pulp --watch run. After modifying the contents of src/Main.purs, pulp will automatically recompile and run the module.

Installing code bundler

Since we'd like to build a JavaScript application that integrates with PureScript, a code bundler will come in handy.

Parcel helps to effortlessly transpile ES6 code, bundle modules and automatically reload the code in the browser with no extra configuration. You can install Parcel with yarn add parcel.

Defining npm script and running the bundler

Once installed, it is often a good practice to add a script to the package.json file so that we can easily run the bundler. We're going to define dev script that will bundle the code and serve the application on port 1234 after running yarn run dev in the terminal.

// package.json
"scripts": {
  "dev": "parcel serve src/index.html"
}

Next, we create src/index.html

<!-- src/index.html -->
<html>
  <head>
    <title>PureScript Application</title>
  </head>

  <body>
    <script src="./index.js"></script>
  </body>
</html>

And src/index.js

// src/index.js
console.log('Hello from JavaScript');

Now, after executing yarn run dev, a very basic JavaScript application is up and running on http://localhost:1234/.

Calling PureScript from JavaScript

Now, the final step. We'd like to execute PureScript code from src/Main.purs in our JavaScript application. We still want yarn run dev to be running in the background.

However, instead of running pulp --watch run and executing the PureScript code, we're going to run pulp --watch build to build the code and skip the execution part.

The PureScript module

Now, when both commands are running in the background, we can have a look at our src/Main.purs module.

module Main where

import Prelude
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)

main :: forall e. Eff (console :: CONSOLE | e) Unit
main = do
  log "Hello from PureScript!"

Essentially, we can tell the module is named Main and it only has a single method called main. It imports a bunch of other modules in order to tell the compiler the main function is effectful and the particular side effect involved is JavaScript console. It also imports the log function which takes a string and prints it in the JavaScript console. The main function doesn't produce any value hence the Unit in the type definition.

Importing the module

Next, after we understood the PureScript module, we can import the compiled output from our JavaScript file.

// src/index.js
const Main = require('../output/Main/index');

console.log('Hello from JavaScript');

Main.main();

After opening the browser window again, we can see both the JavaScript code we wrote by hand and the JavaScript code produced by the PureScript compiler both executed and printed text to the JavaScript console.

Excellent! We just integrated existing JavaScript code with PureScript. Now we're free to write in both languages. We can also gradually introduce PureScript in some areas of our codebase as we learn the language.

Next steps

We learned integrating PureScript code with JavaScript doesn't have to be a difficult task. Parcel and Pulp make it a simple process. Parcel's documentation also explains how to bundle our application for production.

When it comes to learning PureScript, I highly recommend the "PureScript by Example" book by Phil Freeman, the author of the language. It's a fantastic resource for learning the language as well as functional programming concepts in general!

Good luck!

Migrating code to the Reader Monad

The Reader Monad is one of the famous trio: Reader/Writer/State. They are all really powerful abstractions and fit specific scenarios like being able to read global configuration, being able to trace the execution of the program or being able to run stateful computations.

In this post, I will share how to evolve a program using explicit configuration parameter into a program that uses the Reader Monad.

The base program

Imagine we wrote a Haskell program which:

  1. Establishes a connection to the memcache server using the memcache package
  2. Reads and prints the current value for the counter key
  3. Stores "1" under the counter key
  4. Reads and prints the current value for the above key
  5. Stores "2" under the counter key
  6. Reads and prints the current value for the above key

Here are the necessary imports:

{-# LANGUAGE OverloadedStrings #-}
module Main where

import qualified Database.Memcache.Client as MemcacheClient
import qualified Database.Memcache.Types as MemcacheTypes
import           Data.Maybe (Maybe(..))

A type alias for memcache result:

type MemcacheResult = Maybe (MemcacheTypes.Value, MemcacheTypes.Flags, MemcacheTypes.Version)

A set of methods to establish the connection, store values in and retrieve from the server:

connection :: IO MemcacheClient.Client
connection = MemcacheClient.newClient [MemcacheClient.def] MemcacheClient.def

-- Example: set connection "counter" "1"
set :: MemcacheClient.Client -> MemcacheTypes.Key -> MemcacheTypes.Value -> IO MemcacheTypes.Version
set connection key value = MemcacheClient.set connection key value 0 0

-- Example: get connection "counter"
get :: MemcacheClient.Client -> MemcacheTypes.Key -> IO MemcacheResult
get = MemcacheClient.get

And finally - the actual program:

main :: IO ()
main = do
  conn <- connection

  get conn "counter" >>= print
  set conn "counter" "1"
  get conn "counter" >>= print
  set conn "counter" "2"
  get conn "counter" >>= print

  return ()
  
-- First run:
-- Nothing
-- Just ("1",0,1)
-- Just ("2",0,2)
--
-- Consequent run:
-- Just ("2",0,2)
-- Just ("1",0,3)
-- Just ("2",0,4)

The transition to the Reader Monad

After installing the mtl package we can make use of the Reader Monad to make the above functions a bit easier to use.

We start by adding some additional imports:

import Data.Function (flip)
import Control.Monad.Reader (ReaderT, runReaderT, ask)
import Control.Monad.IO.Class (liftIO)

Next, we change the get and set helper functions:

-- Example: set "counter" "1"
set :: MemcacheTypes.Key -> MemcacheTypes.Value -> ReaderT MemcacheClient.Client IO MemcacheTypes.Version
set key value = do
  connection <- ask
  liftIO $ MemcacheClient.set connection key value 0 0
  
-- Example: get "counter"
get :: MemcacheTypes.Key -> ReaderT MemcacheClient.Client IO MemcacheResult
get key = do
  connection <- ask
  liftIO $ MemcacheClient.get connection key

and finally we change the main function to call runReaderT with the connection used as the configuration/environment being read with ask calls:

main :: IO ()
main = do
  conn <- connection

  flip runReaderT conn $ do
    get "counter" >>= liftIO . print
    set "counter" "1"
    get "counter" >>= liftIO . print
    set "counter" "2"
    get "counter" >>= liftIO . print

  return ()

-- First run:
-- Nothing
-- Just ("1",0,1)
-- Just ("2",0,2)
--
-- Consequent run:
-- Just ("2",0,2)
-- Just ("1",0,3)
-- Just ("2",0,4)

Full code listing: src/Main.hs

Summary

With these small changes, we managed to transform the base program to pass connection to helper functions from one place thanks to how the reader monad works. The actual behavior of the program didn't change.

In the real world, one would pass a record full of configuration options — database host, api endpoints, environment type etc. — to the runReaderT call.

Do you find the second version better or do you think it's worse? Let me know on twitter - @maciejsmolinski.

ReasonML - Http Server in Node.js

Since the JavaScript interop in ReasonML is really powerful nothing stops us from writing some ReasonML for Node.js.

Here's a very simple Node.js server:

module Http = {
  type lib;
  type server;
  type request;
  type response = {. [@bs.meth] "_end": string => unit};
};
[@bs.module]
external http : Http.lib = "http";

[@bs.send.pipe: Http.lib]
external createServer : ((Http.request, Http.response) => unit) => Http.server = "";

[@bs.send.pipe: Http.server]
external listen : int => Http.server = "";
http
  |> createServer((_, res) => res##_end("hello world"))
  |> listen(3333);

The above produces the following JavaScript code:

var Http = require("http");

Http.createServer((function (_, res) {
  return res.end("hello world");
})).listen(3333);

Type Safety

Because we assigned types to each of the external calls, the following code would not compile:

http |> listen(3333);

(* same as listen(3333, http) *)

The compiler will warn us the listen method cannot accept http as a parameter because the type of http is Http.lib whereas listen expects a parameter of type Http.server and the only way to produce a value of this type is to use createServer:

  This has type:
    (Http.server) => Http.server
  But somewhere wanted:
    (Http.lib) => 'a

  The incompatible parts:
    Http.server
    vs
    Http.lib

ReasonML / BuckleScript JavaScript Interop

JavaScript interop in ReasonML / BuckleScript looks intimidating in the beginning but after some time you learn to love it.

I'm quite amazed by it. The following ReasonML code:

[@bs.module]
external fetch : string => Js.Promise.t('a) = "isomorphic-unfetch";

fetch(url)
|> Js.Promise.then_(response => Js.Promise.resolve(response##text()))
|> Js.Promise.then_(data => Js.Promise.resolve(Js.log(data)));

produces this JavaScript:

var IsomorphicUnfetch = require("isomorphic-unfetch");

IsomorphicUnfetch(url).then((function (response) {
    return Promise.resolve(response.text());
})).then((function (data) {
    return Promise.resolve((console.log(data), /* () */0));
}));