Fable, czyli jak kompilować F# do JavaScript

TypeScript, CoffeeScript, Babel, traceur, ClojureScript, … . Co je łączy? Kompilują kod napisany w alternatywnych językach do “klasycznego” JavaScriptu.

Dlaczego są popularne ? Z jednej strony JavaScript potrafi zaskoczyć, o czym pisałem już jakiś czas temu tutaj. Z drugiej strony, w dużych projektach chcemy często pisać dobrze zarządzalny i skalowalny kod.

Biorąc po uwagę liczne “syntactic sugars” obecne w innych językach programowania i kompilator pilnujący składni, bardzo często jesteśmy w stanie mocno zyskać na czasie podczas tworzenia kodu.

A jeśli już jesteśmy przy konstrukcjach języka ułatwiających życie i poprawiających czytelność, to IMO jednym z najlepszych języków pod tym względem jest F#. I F# do JavaScript również można kompilować.

Projekt nazywa się fable (http://fable.io/) i można go sobie zainstalować jako pakiet npm poleceniem

npm install -g fable-compiler

Jeśli ktoś jest już dłużej w świecie F# to myślę, że wystarczającą zachętą do spojrzenia na fable będzie fakt, że jednym z aktywniejszych kontrybutorów jest sam Tomas Petricek.

Przykład

Poniżej przykład prostego modułu F#, który moglibyśmy chcieć skompilować do JavaScript. Moduł zawiera jedną funkcję walidującą numer NIP. 

module nipValidationModule
open System.Text.RegularExpressions

let validateNip (nip:string) =
    let weights = [6; 5; 7; 2; 3; 4; 5; 6; 7]
    let regex = new Regex "[0-9]{10}"
    match regex.IsMatch nip with
        | true ->
            let lastDigit = nip |> Seq.last
            let checksum = 
                nip |> Seq.map (string >> int)
                    |> Seq.take weights.Length
                    |> Seq.zip weights
                    |> Seq.fold(fun acc tuple -> acc + fst(tuple) * snd(tuple)) 0
            (checksum % 11) = (string >> int) lastDigit            
        | _ -> false

Kod powyżej jest napisany tak, by wykorzystać zarówno to, co dostępne w .NET BCL jak i konstrukcje charakterystyczne dla F#. Mamy tu więc:

  • klasę Regex z System.Text.RegularExpressions
  • prosty pattern matching do sprawdzenia wyniku regexa
  • operatory forward pipe (|>) i forward composition (>>)
  • wykorzystanie funkcji z Microsoft.Fsharp.Collections, takich jak map, zip, czy fold
  • operacje na tuplach (fst, snd)

Co na to fable ? Wpisujemy:

>fable nipValidationModule.fsx
fable-compiler 0.7.7: Start compilation...
Compiled nipValidationModule.js at 21:01:23

I… poradził sobie 🙂

Tak wygląda kod JavaScript po kompilacji: 

import { ofArray } from "fable-core/List";
import { isMatch, create } from "fable-core/RegExp";
import { map, take, zip, fold, last } from "fable-core/Seq";
import { toString } from "fable-core/Util";
export function validateNip(nip) {
  var weights = ofArray([6, 5, 7, 2, 3, 4, 5, 6, 7]);
  var regex = create("[0-9]{10}");
  var matchValue = isMatch(regex, nip);

  if (matchValue) {
    var lastDigit = last(nip);
    var checksum = fold(function (acc, tuple) {
      return acc + tuple[0] * tuple[1];
    }, 0, function (source2) {
      return zip(weights, source2);
    }(take(weights.length, map(function ($var1) {
      return Number.parseInt(toString($var1));
    }, nip))));
    return checksum % 11 === function ($var2) {
      return Number.parseInt(toString($var2));
    }(lastDigit);
  } else {
    return false;
  }
}

Załadował nam co prawda kilka modułów, ale to głównie dlatego, że JS sam w sobie nie zawiera takich operacji jak fold czy zip. Biblioteka fable-core zajmuje około 20KB, a więc nie jest to raczej jakiś ogromny narzut.

Pattern matching tłumaczony jest do zwykłego bloku if-else. Forward composition i forward pipe sprowadziły się do zagnieżdżonych wywołań funkcji i inline’owych definicji, które zastępują lambdy. Tracimy znacznie na czytelności, ale kod dalej jest poprawny i o to nam głównie chodzi.

Co dalej ?

Analizując dokumentację możemy dowiedzieć się, ze:

  • fable generuje source mapy, a więc pliki fsx możemy spokojnie debugować w przeglądarce, bez konieczności analizy generowanego kodu
  • można kompilować F# bezpośrednio do ES 2015 (domyślnie ES5)
  • podstawowe typy z .NET, takie jak String, DateTime, List, Dictionary są w pełni przetłumaczone do JavaScript wewnątrz fable-core
  • również mnóstwo konstrukcji z samego F# jest przetłumaczonych do JS. Oprócz tych, wymienionych w przykładzie, mamy także odpowiedniki dla m.in unii, rekordów czy mechanizmu jednostek miary
  • Projekt Fable.Core.JsInterop umożliwia z kolei pisanie kodu dynamicznie typowanego po stronie F#

Mamy zatem do dyspozycji potężne narzędzie. Wygodne w użyciu typy z .NET możemy połączyć ze zwięzłym kodem F# i skompilować to wszystko do wszechobecnego JavaScriptu.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s