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
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.
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