Strongly-typed front-end: introduction

Contents

  1. Introduction (you are here)
  2. Experiment 1, darken_color
  3. Experiment 2, simple application

In this little research project I describe my journey through a series of experiments trying out a number of technologies and clashing them against each other.

I have always questioned the real value all those languages that compile to JS, especially TypeScript or Flow, give you.

So I have asked Atlassian front-enders a question:

I need your opinions for my research: what benefits does TypeScript (or Flow, depending on your camp) give you? why do you use it?

The answers I have received varied but the common themes were:

  • we like types! 😍
  • less errors
  • easier refactoring
  • tools & IDEs integration (mainly for code navigation and autocomplete)
  • self-documented or more readable code

The issues I see with TypeScript and Flow are bit closer to the real world:

  • they catch way too few errors - unless your whole project (including 3rd party dependencies) is using the thing correctly, you will see the errors whenever you end up in the layer between native JS and typed code
  • more often than not, error messages are either pointless or hard to read (see the examples below)

I do acknowledge the earlier you catch an error, the cheaper the fix would be. You have to put some effort into writing the typed code, but if it only catches a fraction of errors and only at compile time, then why all the hassle?

“Benefits” of TypeScript

Pointless errors

The most issues I have seen so far happen in what is considered an stdlib:

interface MyClass {
  id: string;
  val: number;
}

const a: MyClass[] = [
  { id : 'moo', val: 1 }, 
  { id: 'foo', val: -1 },
  { id: 'bar', val: 3.14 },
];

const counts = a.reduce((acc, e) => {
  if (acc.has(e.id)) {
    acc.set(e.id, acc.get(e.id) + e.val);
  } else {
    acc.set(e.id, e.val);
  }
  
  return acc;
}, new Map<string, number>());

Here we are reducing a list of objects. TS is freaking out every time you are using Map (however, it is a natively supported type, IIRC) - calling map.get() will always produce T | undefined type, unless you explicitly tell TS the type is T by using the as operator:

acc.set(e.id, acc.get(e.id) + e.val); // ERROR: Object is possibly 'undefined'.

Adding an explicit check does not have any effect:

if (acc.has(e.id) && acc.get(e.id) !== undefined) {
  acc.set(e.id, acc.get(e.id) + e.val); // TS does not give a damn: Object is possibly 'undefined'.
}

whereas

acc.set(e.id, acc.get(e.id) as number + e.val); // OK

But the issue is: if you do not add the check, the value in fact might be undefined.

Flow has flaws here too:

/* @flow */

interface MyClass {
  id: string;
  val: number;
}

const a: MyClass[] = [
  { id : 'moo', val: 1 }, 
  { id: 'foo', val: -1 },
  { id: 'bar', val: 3.14 },
];

const counts = a.reduce((acc, e) => {
  if (acc.has(e.id) && acc.get(e.id) !== undefined) { // ERROR: Cannot perform arithmetic operation because undefined [1] is not a number. [unsafe-addition]
    acc.set(e.id, acc.get(e.id) + e.val);
  } else {
    acc.set(e.id, e.val);
  }
  
  return acc;
}, new Map<string, number>());

But it provides a bit more context about the issue:

    16:     acc.set(e.id, acc.get(e.id) + e.val);
                          ^ Cannot perform arithmetic operation because undefined [1] is not a number. [unsafe-addition]
        References:
        [LIB] ..//static/v0.135.0/flowlib/core.js:617:     get(key: K): V | void;
                                                                            ^ [1]

Both Flow and TS work fine if you extract the .get call result to a variable and add a check for undefined:

interface MyClass {
    id: string;
    val: number;
}

const a: MyClass[] = [
    { id : 'moo', val: 1 }, 
    { id: 'foo', val: -1 },
    { id: 'bar', val: 3.14 },
];

const counts = a.reduce((acc, e) => {
    const prevVal = acc.get(e.id);

    if (prevVal !== undefined) {
        acc.set(e.id, prevVal + e.val);
    } else {
        acc.set(e.id, e.val);
    }

    return acc;
}, new Map<string, number>());

Implementation-specific errors

In order to understand this error message, you have to know how enums are implemented in TypeScript:

enum Figure {
  RECTANGLE,
  SQUARE,
  CIRCLE,
}

const area: Record<Figure, Function> = {
  [Figure.RECTANGLE]: (w: number, h: number) => w * h,
  [Figure.CIRCLE]: (r: number) => Math.PI * r * r,
  // ERROR: Property '1' is missing in type '{ 0: (w: number, h: number) => number; 2: (r: number) => number; }' but required in type 'Record<Figure, Function>'.
};

console.log(area);

Enums in TS are backed by numbers, by default. In order for that error above to make sense, you have to provide some sort of a reasonable (.toString()-backed) value for enum values:

enum Figure {
  RECTANGLE = 'RECTANGLE',
  SQUARE = 'SQUARE',
  CIRCLE = 'CIRCLE',
}

const area: Record<Figure, Function> = {
  [Figure.RECTANGLE]: (w: number, h: number) => w * h,
  [Figure.CIRCLE]: (r: number) => Math.PI * r * r,
  // ERROR: Property 'SQUARE' is missing in type '{ RECTANGLE: (w: number, h: number) => number; CIRCLE: (r: number) => number; }' but required in type 'Record<Figure, Function>'.
};

console.log(area);

Runtime is imperfect

You might have typed every single bit of your project and all the 3rd party dependencies. And you did it right. This still does not guarantee you won’t have Can not read property XXX of undefined or XXX is not a function at run time.

Type system won’t really save you, if you only have covered some of the use cases, but the user ended up in uncovered one:

import React, { useState, useCallback } from 'react';

enum Shape {
  SQUARE = 'SQUARE',
  CIRCLE = 'CIRCLE',
}

const AREA: Record<Shape, Function> = {
  [Shape.SQUARE]: (side: number) => side * side,
  [Shape.CIRCLE]: (r: number) => Math.PI * r * r,
};

export default () => {
  const [shape, setShape] = useState<Shape>(null);
  const [value, setValue] = useState<number>(0);
  const [area, setArea] = useState<number>(0);

  const onShapeChanged = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
    setShape(e.target.value);
  }, []);

  const onValueChanged = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    setValue(parseFloat(e.target.value));
  }, []);

  const onSubmit = useCallback(() => {
    setArea(AREA[shape](value));
  }, [shape, value]);

  return (
    <div>
      <select onChange={onShapeChanged}>
        <option value="">Choose shape</option>

        {Object.keys(AREA).map(shape => (<option value={shape}>{shape}</option>))}
      </select>

      <input value={value} onChange={onValueChanged} />

      <button onClick={onSubmit}>Calculate area</button>

      <div>
        Area: {area}
      </div>
    </div>
  );
};

This is a quite simple application (sandbox), built on top of the previous example with enums and records in TypeScript.

There are at least two uncovered scenarios in this application, resulting in errors:

  1. when user does not select a shape and clicks “calculate”, the TypeError: AREA[shape] is not a function will be thrown
  2. when user types anything but number in the input, the value immediately becomes NaN; if user then clicks “calculate”, an error won’t be thrown (since the app does not use the calculation result in any way), but the area calculated will also be NaN; for this example this is fine, but imagine using the value further down the line in some financial calculations

This is a trivial synthetic example and the errors might be easy to spot and fix, but the important question is: did TypeScript help you find those errors?

If you set up TSLint, you might have some errors caught:

Argument of type 'null' is not assignable to parameter of type 'Shape | (() => Shape)'.ts(2345)
Argument of type 'string' is not assignable to parameter of type 'SetStateAction<Shape>'.ts(2345)
Type 'null' cannot be used as an index type.ts(2538)

Unless you do the right thing, you might end up fixing those scenarios. But instead, I often see solutions like these (sandbox):

const [shape, setShape] = useState<Shape | null>(null);

// ...

setShape(e.target.value as Shape);

// ...

setArea(shape ? AREA[shape](value) : 0);

Those solutions do solve a subset of errors, at a cost of readability and potential other errors.

There are no classes in JavaScript

Found this one recently, apparently TypeScript classes are same as JavaScript (ES6) classes:

namespace TypeScriptIsGarbage {
  export class A {};

  export class B {};

  export const a = (): A => new B(); // perfectly fine

  export const b = (): B => new A(); // perfectly fine
}

namespace TypeScriptIsJavaScript {
  export enum Types {
    A,
    B,
  }

  export type A = { type: Types.A };

  export type B = { type: Types.B };

  export const a = (): A => ({ type: Types.B }); // Type 'Types.B' is not assignable to type 'Types.A'.

  export const b = (): B => ({ type: Types.A }); // Type 'Types.A' is not assignable to type 'Types.B'.
}

This one is actually quite serious, if your application relies on type safety and objective-oriented-design.

Try doing it in C# (which TS tries to inherit from, iirc) and yo’ll get sane compile-time errors:

using System;

class A {}

class B {}

class Main {
  A createA() {
    return new B(); // Cannot implicitly convert type `B` to `A`
  }

  B createB() {
    return new A(); // Cannot implicitly convert type `A` to `B`
  }

  public static void Main(string[] args) {}
}

IDE integration is awesome

Recently I had to use both Cypress and Jest in a project of mine. Cypress was used for E2E tests and Jest was used for unit-tests. And they both provide some sort of assertion framework (think all those expect() calls).

And apparently their definitions are different and are clashing, since my VSCode looks like this:

TS definitions errors in VSCode TS definitions errors in VSCode

Apparently, I needed two separate tsconfig.json files, for each specific set of tests to even compile the thing. Which is still not recognized by VSCode.

Errors can be found and eliminated early

The helpfulness of the error messages by TS compiler is far from perfect. And same holds for TSLint.

And in some cases (as with type checks), they are even completely missing, so good luck finding out why the application does not work.

It is possible to add a bunch of debugger statements, breakpoints and console.log()s to the code. But what is the benefit of that complex setup and extra overhead of typing every single line then?

Experiment #1: mismatching type handling & error helpfulness

Contents

  1. Introduction
  2. Experiment 1, darken_color (you are here)
  3. Experiment 2, simple application

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
  • 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)
  }
}

ReasonML

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

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

Contents

  1. Introduction
  2. Experiment 1, darken_color
  3. Experiment 2, simple application

In F# world, there is a framework called Fable. It allows one to compile their F# code to JavaScript. There is a built-in package for React, but Fable developers themselves suggest using Elmish, which is a framework similar to Elm, just suited for F#.

A sample Elmish application in the online editor looks like this:

module Elmish.SimpleInput

(**
Minimal application showing how to use Elmish
You can find more info about Emish architecture and samples at https://elmish.github.io/
*)

open Fable.Core.JsInterop
open Fable.React
open Fable.React.Props
open Elmish
open Elmish.React

// MODEL

type Model =
    { Value : string }

type Msg =
    | ChangeValue of string

let init () = { Value = "" }, Cmd.none

// UPDATE

let update (msg:Msg) (model:Model) =
    match msg with
    | ChangeValue newValue ->
        { model with Value = newValue }, Cmd.none

// VIEW (rendered with React)

let view model dispatch =
    div [ Class "main-container" ]
        [ input [ Class "input"
                  Value model.Value
                  OnChange (fun ev -> ev.target?value |> string |> ChangeValue |> dispatch) ]
          span [ ]
            [ str "Hello, "
              str model.Value
              str "!" ] ]

// App
Program.mkProgram init update view
|> Program.withConsoleTrace
|> Program.withReactSynchronous "elmish-app"
|> Program.run

One can easily see the similarities to Elm (or so I think).

Read more

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

Contents

  1. Introduction
  2. Experiment 1, darken_color
  3. Experiment 2, simple application

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

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

Contents

  1. Introduction
  2. Experiment 1, darken_color
  3. Experiment 2, simple application

(Heavily over-opinionated statement) Elm forces you to handle error scenarios when writing the code.

Sandbox

This is pretty much a translation of a TypeScript code from above:

module Main exposing (..)

import Browser
import Html exposing (Html, button, div, text, input, select, option)
import Html.Attributes exposing (value)
import Html.Events exposing (onClick, onInput)

-- util

type Shape = Circle | Square

calculateArea : Shape -> Float -> Float
calculateArea shape value =
  case shape of
    Circle -> pi * value * value
    
    Square -> value * value
    
-- MAIN

main =
  Browser.sandbox { init = init, update = update, view = view }

-- MODEL

type alias Model = { shape: Shape, value: Float, area: Float }

init : Model
init = { shape = "", value = 0, area = 0 }

-- UPDATE

type Msg
  = ShapeChanged Shape
  | ValueChanged Float
  | CalculateArea

update : Msg -> Model -> Model
update msg model =
  case msg of
    ShapeChanged shape ->
      { model | shape = shape }

    ValueChanged value ->
      { model | value = value }
      
    CalculateArea ->
      { model | area = (calculateArea model.shape model.value) }

-- VIEW

onShapeChanged : String -> Msg
onShapeChanged shape = 
  case shape of
    "circle" -> ShapeChanged Circle
    "square" -> ShapeChanged Square

onValueChanged : String -> Msg
onValueChanged value = ValueChanged (Maybe.withDefault 0 (String.toFloat value))

view : Model -> Html Msg
view model =
  div []
    [ select [ onInput onShapeChanged ] [ 
      option [ value "" ] [ text "Choose shape" ], 
      option [ value "circle" ] [ text "Circle" ],
      option [ value "square" ] [ text "Square" ] ]
    , input [ value (String.fromFloat model.value), onInput onValueChanged ] []
    , button [ onClick CalculateArea ] [ text "Calculate area" ]
    , div [] [ text ("Area: " ++ (String.fromFloat model.area)) ]
    ]

Note that it won’t compile:

-- TYPE MISMATCH ----------------------------------------------- Jump To Problem

Something is off with the body of the `init` definition:

29| init = { shape = "", value = 0, area = 0 }
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The body is a record of type:

    { area : Float, shape : String, value : Float }

But the type annotation on `init` says it should be:

    Model
Read more

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

Contents

  1. Introduction
  2. Experiment 1, darken_color
  3. Experiment 2, simple application

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. In this case I have picked purescript-react-dom.

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

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