Strongly-typed front-end: experiment 2, simple application, in PureScript

In PureScript world there are quite a few libraries for React. And all of them have terrible (or rather non-existent) documentation, so I had to use as much intuition as outdated and barely working code samples.

Initial application structure:

module Main where

import Prelude

import Control.Monad.Eff

import Data.Maybe
import Data.Maybe.Unsafe (fromJust)
import Data.Nullable (toMaybe)

import Effect (Effect)
import Effect.Console (log)

import DOM (DOM())
import DOM.HTML (window)
import DOM.HTML.Document (body)
import DOM.HTML.Types (htmlElementToElement)
import DOM.HTML.Window (document)

import DOM.Node.Types (Element())

import React

import React.DOM as DOM
import React.DOM.Props as Props

type Shape = Circle | Square

calculateArea :: Maybe Shape -> Float -> Float
calculateArea Nothing _ = 0
calculateArea (Just Circle) value = pi * value * value
calculateArea (Just Square) value = value * value

getShape :: String -> Maybe Shape
getShape "circle" = Just Circle
getShape "square" = Just Square
getShape _ = Nothing

onShapeChanged ctx evt = do
  writeState ctx { shape: getShape ((unsafeCoerce evt).target.value) }

onCalculateAreaClicked ctx evt = do
  { shape, value } <- readState ctx
  writeState ctx { area: calculateArea shape value }

areaCalculator = createClass $ spec { shape: Nothing, value: 0, area: 0 } \ctx -> do
  { shape, value, area } <- readState ctx
  return $ DOM.div [] [
    DOM.div [] [
      DOM.select [ Props.onChange (onShapeChanged ctx) ] [
          DOM.option [ Props.value "" ] [ DOM.text "Select shape" ],
          DOM.option [ Props.value "circle" ] [ DOM.text "Circle" ],
          DOM.option [ Props.value "square" ] [ DOM.text "Square" ]
      ],
      DOM.input [ Props.value (show value) ] [],
      DOM.button [ Props.onClick (onCalculateAreaClicked ctx) ] [ DOM.text "Calculate area" ]
    ],
    DOM.div [] [
      DOM.text ("Area: " ++ (show area))
    ]
    ]

main = container >>= render ui
  where
  ui :: ReactElement
  ui = createFactory areaCalculator {}

  container :: forall eff. Eff (dom :: DOM | eff) Element
  container = do
    win <- window
    doc <- document win
    elt <- fromJust <$> toMaybe <$> body doc
    return $ htmlElementToElement elt
Read more

Strongly-typed front-end: experiment 2, simple application, in ReasonML

For the sake of experiment, I have decided to implement the very same application in ReasonML โ†’ ReScript by Facebook.

Starting the React Hooks example on Try ReasonML website, you get this code, which resembles some of the React features, just in a slightly weird syntax:

[@bs.config {jsx: 3}];

module Counter = {
  [@react.component]
  let make = (~name) => {
    let (count, setCount) = React.useState(() => 0);

    <div>
      <p> {React.string(name ++ " clicked " ++ string_of_int(count) ++ " times")} </p>
      <button onClick={_ => setCount(_ => count + 1)}>
        {React.string("Click me")}
      </button>
    </div>
  };
};

ReactDOMRe.renderToElementWithId(<Counter name="Counter" />, "preview");

Starting off by defining the enum type for shape:

type Shape = Circle | Square;

And immediately getting an error:

Line 4:8-12 A type name must start with a lower-case letter or an underscore

That one is easy to fix:

type shape = Circle | Square;

Now, add some markup:

[@react.component]
let make = (~name) => {
  let (_shape, setShape) = React.useState(() => None);
  let (value, setValue) = React.useState(() => 0.0);
  let (area, setArea) = React.useState(() => 0.0);

  <div>
    <select>
      <option value=""> Choose shape </option>
      <option value="circle"> Circle </option>
      <option value="square"> Square </option>
    </select>
    <input value={value} />
    <p> {React.string(string_of_float(area))} </p>
    <button>
      {React.string("Calculate")}
    </button>
  </div>
};
Read more

Experiment #1: mismatching type handling & error helpfulness

For a sake ๐Ÿถof science experiment, I have converted one function of a library I created long time ago to multiple languages that compile to JS and called it with various values.

The function is simple - it takes a color represented as a HEX string and converts it to { r, g, b } object.

The test is relatively big - it passes various numbers (integer and floating point, negative and positive), booleans, objects, arrays, obvious candidates - null and undefined and incorrect string.

The implementations are made with:

  • Scala.js
  • ReasonML & BuckleScript โ†’ ReScript
  • F#
  • PureScript
  • TypeScript
  • Elm

Implementations

Scala.JS

package darken_color

import scala.scalajs.js
import scala.scalajs.js.annotation._

class RGB(val r: Int, val g: Int, val b: Int) extends js.Object

@JSExportTopLevel("DarkenColor")
object DarkenColor {
  @JSExport
  def hex2rgb(s: String): RGB = {
    val re = """^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$""".r

    val rgbStr = s match {
      case re(rStr, gStr, bStr) => Some((rStr, gStr, bStr))
      case _ => None
    }

    rgbStr.map (x => new RGB(Integer.parseInt(x._1, 16), Integer.parseInt(x._2, 16), Integer.parseInt(x._3, 16))).getOrElse(null)
  }
}

ReScript

type rgb = {
  r: int,
  g: int,
  b: int,
}

let parse_hex = s => int_of_string("0x" ++ s)

let hex2rgb = hex =>
  Js.Re.fromString("^#?([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$")
    -> Js.Re.exec_(hex)
    -> Belt.Option.map (Js.Re.captures)
    -> Belt.Option.map (Js.Array.map (Js.Nullable.toOption))
    -> Belt.Option.map (x => Js.Array.sliceFrom(1, x))
    -> Belt.Option.map (Js.Array.map (x => Belt.Option.map(x, parse_hex)))
    -> (matches => switch matches {
      | Some([ Some(r), Some(g), Some(b) ]) => Some({ r: r, g: g, b: b })
      | _ => None
    })

PureScript

module DarkenColor where

import Prelude (join, map, ($), (<#>), (>>=), (>>>))

import Data.Array (catMaybes)
import Data.Array.NonEmpty (drop)
import Data.Int (fromStringAs, hexadecimal)
import Data.Maybe (Maybe(..))
import Data.Nullable (Nullable, toNullable)
import Data.Either (hush)
import Data.String.Regex (regex, match)
import Data.String.Regex.Flags (ignoreCase)

type RGB =
  {
    r :: Int,
    g :: Int,
    b :: Int
  }

constructRGB :: Array Int -> Maybe RGB
constructRGB [ r, g, b ] = Just { r: r, g: g, b: b }
constructRGB _ = Nothing

hex2rgb :: String -> Nullable RGB
hex2rgb hexString =
  toNullable $
  ((hush >>> join) $ (regex "^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$" ignoreCase) <#> (\re -> (match re hexString)))
  <#> (drop 1)
  <#> catMaybes
  <#> (map (fromStringAs hexadecimal))
  <#> catMaybes
  >>= constructRGB

F#

module DarkenColor

open System.Text.RegularExpressions

type RGBType = { r: int16; g: int16; b: int16 }

let hex2rgb (hex: string) =
    let m = Regex.Match(hex, "^#?([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$")
    if m.Success then
        m.Groups
        |> Seq.cast<Group>
        |> Seq.skip 1 // zero capture group is always the full string, when it matches
        |> Seq.map (fun m -> m.Value)
        |> Seq.map (fun x -> System.Convert.ToInt16(x, 16))
        |> Seq.toList
        |> (function
            | r :: g :: b :: [] -> Some { r = r; g = g; b = b }
            | _ -> None)
    else None

TypeScript

interface RGBType {
  r: number;
  g: number;
  b: number;
}

/**
  * Converts a HEX color value to RGB by extracting R, G and B values from string using regex.
  * Returns r, g, and b values in range [0, 255]. Does not support RGBA colors just yet.
  *
  * @param hex The color value
  * @returns The RGB representation or {@code null} if the string value is invalid
  */
const hex2rgb = (hex: string): RGBType => {
  // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
  const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;

  hex = hex.replace(shorthandRegex, (_match, r, g, b) => {
    return r + r + g + g + b + b;
  });

  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);

  if (!result) {
    return undefined;
  }

  return {
    r: parseInt(result[1], 16),
    g: parseInt(result[2], 16),
    b: parseInt(result[3], 16)
  };
}

export { hex2rgb };

Elm

module DarkenColor exposing (..)

import List
import Maybe
import Maybe.Extra
import Regex

type alias RGBType = { r: Int, g: Int, b: Int }

hex2rgb : String -> Maybe RGBType
hex2rgb hex =
    Regex.fromString "^#?([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$"
        |> Maybe.map (\regex -> Regex.find regex hex)
        |> Maybe.map (List.map .match)
        |> Maybe.map (List.map String.toInt)
        |> Maybe.andThen (Maybe.Extra.combine)
        |> Maybe.andThen constructRGB

constructRGB list =
    case list of
        [ r, g, b ] -> Maybe.Just { r = r, g = g, b = b }
        _ -> Maybe.Nothing

For fair comparison, the implementation is kept same (no platform-specific code, except Option in functional languages) and every single bundle is processed with Webpack 4.

The test checks both the result and the assumes no exception is thrown, even when the input is incorrect. For the interest sake, the exceptions thrown as well as bundle sizes will be listed below.

Read more

Rogue bomber

This is a yet another show-off blog.

Yet another one-day-build, made specifically for not-so-exciting ShipIt-51 hackathon we are having at work right now.

This is a terrible code written in JS in matter of some 8 hrs.

Click the button below to start playing.

Gantt chart. Part 3

I have been writing about and improving on my Gantt chart implementation for quite some time now.

It all started with this (blog):

First revision of Gantt chart

Then I added few features (blog):

Second revision of Gantt chart

Back then I have promised to re-write the implementation in Canvas. And so I did.

Curious fact: I did not plan on doing this at this time - it was a private email from darekeapp12 who wrote this:

I have looked at your blog for Gantt chart and implementation. I am doing a project using React and Node/Express and need to implement Gantt chart that is also draggable i.e. the bars can be moved and also resized from either end. I have researched heavily but could not find anything. I am thinking if there is no library, something could be built from scratch.

The sender seemed dodgy (DAREKE app) so I did not want to reply to avoid unnecessary spam subscriptions, but I was happy to improve my old project.

Here are few new features and improvements added to the chart:

  • scrolling and zooming in & out is now a thing
  • you can drag the milestones around and stretch & shrink them
  • an event will be fired whenever a milestone is changed (moved / stretched / shrinked)
  • dependencies are now typed: start-to-start, end-to-end or end-to-start are the only supported types

Here, you can even play around with it now!

More about the implementation specifics under the cut.

Read more

ShootThem! revival

Quite some time ago I dag out the sources of an old game of mine, ShootThem! made back when I was at high-school, around 2006.

It has been over a decade ever since I made that game and I had enough inspiration to revisit the code once again.

This is a short update blog about what it used to be and what it became as of now.

Read more

Erlang example 2.0

Quite some time ago I’ve published a blogpost about Erlang. It claimed to present a short intro to distributed programming in Erlang. But it turned to be a very simple communication application, nothing super-exciting.

In this post I would like to elaborate more on the topic of Erlang, for a number of reasons:

  • it is a pretty simple language itself
  • the distributed systems topic gets more and more of my attention these days
  • when I was looking at Erlang, I would love to see a more advanced tutorial myself (more practical things and more Erlang platform features showcased)
Read more

Vim keystrokes cheatsheet

The time has come for me to list few of the commands and keystrokes that I use (and the ones that I don’t but would like to start) in Vim.

I am actually running a Vim plugin for Visual Studio Code (this and the previous blogs are actually written with this plugin ON) at this very moment.

Bear ๐Ÿป in mind: this blog is aboout keystrokes only, it is not about plugins or configuration I use - I shall cover that one day.

Things I know by heart

  • h, j, k, l for slow but precise character-wise movement
  • w, b for faster forward (and, correspondingly) backward word-by-word movement
  • $ and ^ to go to the last and first word character of the line
  • gg and G to go to the beginning and the end of the file
  • O (capital o) to create a blank line and go to INSERT mode above and below (lower-case o) the cursor
  • f, F and t and T for single character lookup within the current line
  • / and ? to search forwards and backwards
  • c (followed by the object) to change the object; this expands to the following few commands:
    • cw to change the current word under cursor
    • cit to change the text within the current XML tag
    • ca' to change the text surrounded by single quote
    • ci< to change the text surrounded with &lt; and &gt;
    • ci{symbol} to change the text surrounded by symbol, which could be ', ", &#96;; you can also use b( for block of text, surrounded by braces, B{ for block of text surrounded by curly braces or p for paragraph, all instead of symbol
  • v to enter the VISUAL mode, followed by the command:
    • v{select}c immediately change the selection
    • v{select}d cut the selected text
  • y and p copy and paste the selected text (lower-case p pastes above the current line, capital-case P pastes below; capital-case Y copies the entire line, so the duplicate line command in Vim is Y, P)
  • {number}{command} repeat the command number times
  • . (the period or dot symbol) repeats the last command
  • x to remove the character under cursor
  • r{char} to replace the character under cursor with char
  • A to go to the end of the line and enter INSERT mode (“append”)
  • u and Ctrl+r to undo and redo actions
  • > and < adds or removes the indentation

Things that I am still getting used to

  • {number}{motion} instead of h, j, k, l
  • a instead of i to enter INSERT mode after the cursor (as opposed to i which enters INSERT mode before the cursor)
  • H, M and L to go to the top, middle and the bottom of the screen (High, Mid and Low)
  • * and # to search for the word under cursor forwards and backwards
  • {count}/{query}โŽ to go to the count-th occurrence of query; it is same as searching with / and then hitting n count times
  • gd navigates to a definition of an entity under the cursor
  • gf navigates to the path under cursor
  • % moves the cursor to the matching brace, bracket or curly brace
  • g~ toggle the case
  • = format the selection
  • gU makes the selection uppercase

Gantt chart with D3. Part 2

UPDATE: there is a follow-up to this blog, Gantt chart with Canvas.

This is a follow-up to the blog I wrote a bit over three years ago, Gantt chart with D3

In the original blog I claimed to implement something like this:

Yet I ended up implementing something more like this:

Does not look quite same, right? It also does not work quite same and lacks few quite important features too.

Don’t get me wrong, the original implementation did serve project needs, but it was not something anybody could simply use in their project management software and expect customers to love it.

Hence I came up with these complaints about the implementation:

Namely, there are three main issues that I see:

  1. there is a place for mistakes: milestones are allowed to depend on later milestones or simultaneously going ones
  2. there is no clear distinction between the dates each specific milestone starts or ends
  3. if the milestones overlap with current timeframe, current day is not highlighted on the chart (and that often is useful)

Apart from that, there are few technical challenges preventing his whole thing from becoming a real application component:

  • dependency lines look ugly with those sharp corners
  • the implementation is not based on any framework neither does it declare its dependencies (like D3 or MomentJS)

Now I want to revise the original implementation and make it a bit more usable, just like this:

Read more

A response to response to hello world

Recently I’ve received an email from StackOverflow newsletters with a link to a quite controversial (at first glance) blog, A response to Hello World by Caleb Doxsey. This blog is a response to another curious read, Hello world by Drew DeVault.

In the former article, author compared the performance of a tiny “Hello, World” program in Assembly to the same program in Go. He then tried to optimize the program in Go to run faster and towards the end of an article comes up with a program that is faster than its Assembly counterpart.

And this totally makes sense, since if you give it a good read, you will notice that author did optimize the program in Go but did not do that for the Assembly program.

I have decided to burn few hours of my life and jump onto this topic, since, in my opinion, author did not do a fair comparison.

Read more