Introduction au langage de programmation ReScript

Tutoriel d'Introduction à ReScript

Qu'est-ce que ReScript ?

ReScript est un langage de programmation fonctionnel qui compile vers JavaScript. Il offre une syntaxe concise et expressive tout en fournissant des types statiques forts pour améliorer la robustesse et la maintenabilité du code. ReScript est conçu pour être facilement intégré dans les projets JavaScript existants et est utilisé pour le développement web moderne, les applications front-end et back-end.
Avec ReScript, vous pouvez écrire du code qui se compile en JavaScript, avec des fonctionnalités avancées et des capacités puissantes pour le développement web moderne.

Installation

Pour commencer à utiliser ReScript, vous devez installer le compilateur ReScript. Plusieurs méthodes existent, avec `npm` ou bien à partir des sources :
https://github.com/rescript-lang/rescript-compiler
Le projet `melange` propose également une syntaxe `rescript` disponible dans `opam`.

Premier Programme ReScript

Voyons maintenant comment écrire votre premier programme ReScript.
L'extension de fichier pour les fichiers de code source ReScript est `*.res`.
Pour commencer vous pouvez créer un premier fichier `hello.res` avec le code suivant :
let message = "Bonjour, ReScript!"
Js.log(message)
Ce code définit une variable `message` contenant une chaîne de caractères et l'affiche dans la console à l'aide de `Js.log`.
Pour vos premiers essais, lors de votre apprentissage, vous voudrez peut-être exécuter vos premiers script dans votre console avec `node-js`.
$ bsc hello.res > hello.js
$ node hello.js
Bonjour, ReScript!
Gardez à l'esprit qu'une connaissance minimale de Javascript sera probablement nécessaire pour utiliser ReScript.

Console du navigateur

Dans un navigateur, les messages émis avec Js.log(), sont disponibles dans la console du navigateur, celle-ci n'étant pas ouverte par défaut, il faut l'ouvrir pour avoir accès à ces messages, qui sont souvent utiles pour le débugage. Dans le navigateur Google Chrome, la console est accessible dans le sous menu : "Plus d'outils" > "Outils de développement", ou bien avec le raccourcis clavier Ctrl+Maj+I.

Exemple de code ReScript

let add = (a, b) => a + b

let result = add(2, 3)
Js.log(result)
ReScript est un langage à expression, il n'y a donc pas besoin d'instruction `return` dans une fonction, on place directement l'expression de retour, ici `(a + b)`.

Concepts Clés

Types et Inférence de Type

ReScript est fortement typé, mais grâce à l'inférence de type, vous n'avez pas besoin de spécifier les types partout.
let x = 5  // Inféré comme int
let y: float = 3.14
let z: string = "Hello"

Fonctions

Les fonctions en ReScript sont définies en utilisant `let` et la flèche `=>`.
let add = (a: int, b: int): int => a + b

let result = add(2, 3)
Js.log(result)  // Affiche 5

Modules

Les modules permettent d'organiser le code de manière structurée.
module MathUtils = {
  let square = (x: int): int => x * x
}

let fourSquared = MathUtils.square(4)
Js.log(fourSquared)  // Affiche 16

Interopérabilité avec JavaScript

ReScript est conçu pour interagir facilement avec JavaScript. Vous pouvez utiliser les bibliothèques JavaScript existantes et appeler du code JavaScript directement depuis ReScript.
@module("path") external basename: string => string = "basename"

let filename = "/path/to/file.txt"
let base = basename(filename)
Js.log(base)  // Affiche "file.txt"

La syntaxe de base de ReScript

Nous allons couvrir les principaux éléments comme les variables, les fonctions, les types, les conditions, les boucles, et les modules.

Variables

En ReScript, on déclare les variables avec `let` :
let greeting = "Hello, World!"
let number = 36
let isTrue = true

Fonctions

Les fonctions sont définies avec `let` et `=>`.
let add = (a: int, b: int): int => a + b
Les types des paramètres et du retour peuvent souvent être omis grâce à l'inférence de type.
let add = (a, b) => a + b

Types

ReScript est fortement typé. Voici quelques types de base :
- `int` : nombres entiers
- `float` : nombres à virgule flottante
- `string` : chaînes de caractères
- `bool` : booléens (true/false)
Les types de base peuvent être explicitement définis :
let x: int = 5
let y: float = 3.14
let z: string = "Hello"
Les annotations de type peuvent être omis :
let x = 5
let y = 3.14
let z = "Hello"
Pour être inféré comme un `float`, un nombre doit obligatoirement contenir un point, même s'il n'y a pas de partie décimale :
let u = 4.
let v = 4.0
Les opérateurs arithmétiques pour les flottants doivent également être suffixés avec un point.
let r = (4.0 +. 3.0) *. 2.0

Conditions

Les conditions utilisent `if` et `else`.
let isEven = (n: int): bool => {
  if mod(n, 2) == 0 {
    true
  } else {
    false
  }
}
Chacune des deux branches contiennent une expression de retour, correspondant à la valeur de l'ensemble du `if`, ce qui est typique des langages fonctionnels.

Listes et Tableaux

ReScript utilise des listes immuables et des tableaux.
Listes
let myList = list{1, 2, 3, 4, 5}

let firstElement = Belt.List.head(myList)
let restElements = Belt.List.tail(myList)
Tableaux
let myArray = [1, 2, 3, 4, 5]

let firstElement = myArray[0]
myArray[1] = 10  // Changement de valeur
Chaque cellule du tableau doit être de même type.
Voir plus bas pour savoir comment exécuter ce code.
Les Boucles
Les boucles peuvent être réalisées avec `for` et `while`.
Boucle for
for i in 0 to 10 {
  Js.log(i)
}
Boucle while
let counter = ref(0)

while counter.contents < 10 {
  Js.log(counter.contents)
  counter := counter.contents + 1
}
On voit également ici le type référence `ref`, qui est un type modifiable.
Les Tuples
Les tuples regroupent plusieurs valeurs de types possiblement différents, et sont immuables.
let tuple = (1, 2.0, "three")
Le Type Enregistrement
Les enregristrement (record, en anglais) doivent être définit préalablement avec le mot clef `type` :
type person = {
  age: int,
  name: string,
}
Cette structure rassemble des champs nommés.
Immutable Update
let john = { name: "John", age: 32 }

let john = {...john, age: john.age + 1}
Mutable Update
Utilisez le mot clef `mutable` :
type person = {
  name: string,
  mutable age: int
}

let jane = {name: "Jane", age: 22}
jane.age = baby.age + 1
Le Type `option`
Le type option est un type permettant de gérer les cas de valeurs qui peuvent être définie ou non définie.
let v1 = Some(6)
let v2 = None
Ainsi l'accès à un élément d'un tableau en rescript utilise un type option afin de gérer les cas des accès en dehors des indices existants.
let animals = ["cat", "dog", "bird"]

let animal1 = animals[0]  // Some("cat")

let animal2 = animals->Array.get(20)  // None
Les Types Abstraits
Les Variants
Définit des types de données algébriques.
type color = Red | Green | Blue
let my_color = Red
Les variants ont également besoin d'une définition préalable avec le mot clef `type`.
Les Variants Polymorphiques
Similaire aux variants, mais plus flexible.
let color1 = #blue
let color2 = #red

Correspondance de Motifs

Définissons un type algébrique pour représenter des formes géométriques :
type shape =
  | Circle(float)
  | Rectangle(float, float)
Fonction pour calculer l'aire d'une forme :
let area = (s) => {
  switch s {
  | Circle(r) => 3.1415 *. r *. r
  | Rectangle(w, h) => w *. h
  }
}
La correspondance de motifs se réalise avec le mot clef switch.
Les Types Génériques, et Types Paramétrés
type rec tree<'a> =
  | Leaf
  | Node('a, tree<'a>, tree<'a>)
Un type générique est introduit précédé d'une guillemet simple: 'a
Un type paramétré est succédé d'un second type présent entre des chevrons: tree<'a>
Les Modules
Les modules permettent d'organiser le code. Un module est une collection de fonctions et de valeurs.
Définition de module
module MathUtils = {
  let square = (x: int): int => x * x
  let cube = (x: int): int => x * x * x
}
Utilisation de module
let fourSquared = MathUtils.square(4)
let twoCubed = MathUtils.cube(2)
Interopérabilité avec JavaScript
ReScript est conçu pour interagir facilement avec JavaScript.
Appeler une fonction JavaScript
@val external alert: string => unit = "alert"

alert("Hello from ReScript!")
Importer un module JavaScript
@module("path") external basename: string => string = "basename"

let base = basename("/path/to/file.txt")
Js.log(base)  // Affiche "file.txt"

Utilisation des bibliothèques standards

Que se soit pour une exécution en mode console avec Node, ou bien dans un navigateur, vous devrez réaliser correctement l'importation des fichiers correspondants à la bibliothèque standard de ReScript.
let myArray = [1, 2, 3, 4, 5]

let firstElement = myArray[0]
myArray[1] = 10

Js.log(myArray[1])
Par exemple le code ci-dessus sera transpilé comme suit :
$ bsc array.res > array.js
$ cat array.js
// Generated by ReScript, PLEASE EDIT WITH CARE
'use strict';
var Caml_array = require("bs-platform/lib/js/caml_array.js");

var myArray = [ 1, 2, 3, 4, 5 ];

var firstElement = Caml_array.get(myArray, 0);
Caml_array.set(myArray, 1, 10);

console.log(Caml_array.get(myArray, 1));

exports.myArray = myArray;
exports.firstElement = firstElement;
/*  Not a pure module */
L'instruction `require()` est une instruction qui fonctionne avec Node, mais qui ne fonctionne pas dans un navigateur web.
De plus, l'instruction `require()` doit contenir un chemin de fichier correct, relatif ou absolu. Même si le répertoire courant contient le répertoire "bs-platform", il faut remplacer require("bs-platform/ par require("./bs-platform/.
Par exemple le fichier "caml_array.js", si vous avez récupéré les sources du compilateur rescript 8.4.2, ce fichier et les autres fichiers de la lib-standard seront présents dans le répertoire "lib/js/".
./rescript-compiler-8.4.2/lib/js/
Ainsi le chemin pourrait par exemple être définit comme suit, en remplaçant le chemin produit par défaut :
//var Caml_array = require("bs-platform/lib/js/caml_array.js");
var Caml_array = require("../../dev/rescript/rescript-compiler-8.4.2/lib/js/caml_array.js");
Avec un Makefile, cette tache peut s'automatiser de la manière suivante :
BSC = /usr/bin/bsc

%.js: %.res
	$(BSC) -w -27-26-44 -I . $< | sed -e 's!require("bs-platform!require("./bs-platform!g' > $@

clean:
	$(RM) *.cm[ijt]
L'instruction require() étant spécifique à Node, et n'étant pas reconnue par les navigateurs, pour une exécution dans un navigateur, cette instruction doit être supprimée, et remplacée par une autre solution d'importation du code.
Plusieurs méthodes existent, mais une solution possible simple est d'importer le code JavaScript correspondant à la lib-standard ReScript avec le tag html <script> :
<script type="text/javascript" src="./bs-platform.js"></script>
Ici le fichier "lib/js/caml_array.js" exporte ses fonctions comme ceci à la fin du fichier sources :
exports.caml_array_dup = caml_array_dup;
exports.caml_array_sub = caml_array_sub;
exports.caml_array_concat = caml_array_concat;
exports.caml_make_vect = caml_make_vect;
exports.caml_array_blit = caml_array_blit;
exports.get = get;
exports.set = set;
ReScript produisant le code suivant :
Caml_array.set(myArray, 1, 10);
Une solution possible est de le réécrire comme ceci pour une utilisation dans un navigateur :
var Caml_array = {};

Caml_array.caml_array_dup = caml_array_dup;
Caml_array.caml_array_sub = caml_array_sub;
Caml_array.caml_array_concat = caml_array_concat;
Caml_array.caml_make_vect = caml_make_vect;
Caml_array.caml_array_blit = caml_array_blit;
Caml_array.get = get;
Caml_array.set = set;
Il est également recommandé de rassembler tous les fichiers sources du répertoire "lib/js/" que vous utilisez, en un seul fichier javascript, car si vous réalisiez une importation par fichier avec autant de tags html <script>, cela ne donnerait pas un très bon résultat.
En résumé
Voici un exemple complet combinant plusieurs concepts :
// Définition de module
module MathUtils = {
  let square = (x: int): int => x * x
}

// Utilisation de la fonction du module
let result = MathUtils.square(5)
Js.log(result)  // Affiche 25

// Fonction avec condition
let isEven = (n: int): bool => {
  if mod(n, 2) == 0 {
    true
  } else {
    false
  }
}

// Boucle for
for i in 0 to 5 {
  if isEven(i) {
    Js.log(string_of_int(i) ++ " is even")
  } else {
    Js.log(string_of_int(i) ++ " is odd")
  }
}

Arbre Binaire

Exemple d'utilisation d'un arbre binaire :
type rec tree<'a> =
  | Leaf
  | Node('a, tree<'a>, tree<'a>)
 
let rec height = (tree) => {
  switch tree {
  | Leaf => 0
  | Node(_, left, right) => 1 + max(height(left), height(right))
  }
}

let rec insert = (tree, item) => {
  let (key, value) = item
  switch tree {
  | Leaf => Node((key, value), Leaf, Leaf)
  | Node(this, left, right) =>
      let (_key, _) = this
      if key < _key {
        Node(this, insert(left, item), right)
      } else {
        Node(this, left, insert(right, item))
      }
  }
}

let rec search = (tree, key) => {
  switch tree {
  | Leaf => None
  | Node(this, left, right) =>
      let (_key, _value) = this
      if _key == key { Some(_value) }
      else {
        if key < _key { search(left, key) }
        else { search(right, key) }
      }
  }
}

let () = {
  let tree = Leaf
  let tree = insert(tree, (1, "one"))
  let tree = insert(tree, (3, "three"))
  let tree = insert(tree, (2, "two"))
  let tree = insert(tree, (5, "five"))
  let tree = insert(tree, (4, "four"))

  let res = search(tree, 2)
  switch res {
  | None => Js.log("not-found")
  | Some(v) => Js.log(v)
  }
}

Documentation et Ressources

Le site officiel de ReScript se trouve à cette adresse :
  https://rescript-lang.org/
Guide de démarrage rapide :
  https://rescript-lang.org/docs/manual/latest/overview
API ReScript :
  https://rescript-lang.org/docs/manual/latest/api
The ReScript standard library, Belt :
  https://rescript-lang.org/docs/manual/latest/api/belt
The Js module, and its submodules, are made to be very close to Javascript's API :
  https://rescript-lang.org/docs/manual/latest/api/js
Warning: Be careful if you open the modules Js and/or Belt, that they both contain submodules called `List` and `Array` with functions with similar names, but with different signatures.
Un tutoriel montrant comment réaliser un jeu de type Snake avec ReScript :
  Snake ReScript tutorial
Vous pouvez trouver quelques petits exemples sur le site RosettaCode :
  https://rosettacode.org/wiki/Category:ReScript
Projets ReScript hébergés sur GitHub :
  https://github.com/topics/rescript
Avec ces bases, vous devriez être en mesure de commencer à écrire et compiler du code ReScript.
Pour approfondir vos connaissances, explorez davantage la documentation officielle, les tutoriels disponibles en ligne, et les projets ReScript hébergés sur GitHub.
Et pratiquez pour vous familiariser avec les fonctionnalités avancées du langage.

Created with ChatGPT