MOGWAI vient de passer en version 8.6, et cette release apporte deux fonctionnalités attendues de longue date : un système complet de programmation orientée objet, et une primitive d’assertion intégrée. Tour d’horizon.
La POO dans un langage à pile
Si vous suivez MOGWAI depuis un moment, vous savez que le langage est fondamentalement concaténatif et basé sur une pile, inspiré des calculatrices HP RPL. Ajouter la POO à un tel langage soulève une question évidente : est-ce que ça a vraiment du sens ?
La réponse est oui, et voilà pourquoi.
MOGWAI disposait déjà des records (structures clé-valeur nommées), des fonctions, et d’un système de types riche. Ce qui manquait, c’était la possibilité de lier des données et des comportements, de donner un nom à cet ensemble, et de gérer plusieurs instances indépendantes. C’est exactement ce que la v8.6 apporte.
Définir une classe
Les classes se définissent avec le mot-clé class, suivi du nom de la classe, du mot-clé do, et d’un bloc contenant deux sections : private: et public:.
class 'Counter' do
{
private:
{
_step: .number
}
public:
{
value: .number
onInit:
{
[step: (.number 1)] ->params
self->reset:
step self<-_step:
}
increment:
{
self->value: self->_step: + self<-value:
}
reset:
{
0 self<-value:
}
}
}
Les propriétés sont déclarées avec un sigil de type (.number, .string, .bool, .any…). Les méthodes sont déclarées avec un bloc de code { }. La distinction est faite à l’analyse syntaxique sans ambiguïté.
Hooks de cycle de vie
Deux méthodes spéciales sont reconnues par le moteur :
onInit:appelée automatiquement à la création d’une instance avecnew. Elle reçoit les paramètres nommés passés à la création.onFree:appelée automatiquement juste avant la destruction d’une instance avecfree.
Créer et utiliser des instances
# Créer une instance — onInit: est appelée automatiquement
[id: 10 name: "SIBUE"] 'User' new -> '$U1'
# Lire une propriété publique
$U1->name: ?
# Écrire une propriété publique
"DUPONT" &$U1<-name:
# Appeler une méthode publique
$U1->display:
# Vérifier qu'une instance est encore en vie
$U1 isAlive
# Détruire l'instance — onFree: est appelée automatiquement
$U1 free
La notation -> et <- sera naturelle pour quiconque a utilisé la syntaxe des records MOGWAI. Lire depuis une instance, y écrire, appeler une méthode, tout est cohérent avec le reste du langage.
self — l’instance courante dans une méthode
À l’intérieur de n’importe quelle méthode, self est automatiquement disponible et référence l’instance courante :
display:
{
"USER={! self}" eval ?
self->show: # appelle une méthode privée
}
Utiliser self en dehors d’une méthode lève une erreur.
Encapsulation sans héritage
Ce système POO est délibérément simple : pas d’héritage, pas de garbage collector. Vous avez le contrôle total de la création et de la destruction des instances.
Les membres privés sont strictement inaccessibles depuis l’extérieur de la classe. La propriété réservée en lecture seule className: est automatiquement disponible sur chaque instance et retourne le nom de la classe à laquelle elle appartient :
$U1->className: ? # → 'User'
Plusieurs variables peuvent référencer la même instance. Si l’instance est libérée, toutes les variables qui pointent dessus deviennent invalides. Le prédicat isAlive permet de vérifier en toute sécurité avant d’y accéder :
if ($U1 isAlive) then
{
$U1->display:
}
isAlive est une recherche en O(1), il ne lève jamais d’erreur sur une référence morte, il renvoie simplement false.
Validation des paramètres de méthode
MOGWAI propose trois niveaux de validation des entrées dans les méthodes, tous parfaitement utilisables au sein des classes :
->vars: déstructuration simple, sans vérification de type->safeVars: déstructuration avec validation de type->params: record de paramètres nommés avec types et valeurs par défaut optionnelles (le choix naturel pouronInit:)
onInit:
{
[id: .number name: .string index: (.number 0)] ->params
id self<-id:
name self<-name:
index self<-index:
}
mogwai.assert : Les préconditions à la sauce MOGWAI
Le deuxième ajout majeur de la v8.6 est mogwai.assert.
Il vérifie qu’une condition est vraie. Si elle est fausse, il lève l’erreur MW.9 (assert error) et stoppe l’exécution. Si MOGWAI.onError est défini, il sera déclenché automatiquement.
La signature est simple :
condition "message" mogwai.assert
La condition peut être :
- Une liste évaluée automatiquement.
mogwai.assertvérifie qu’exactement un booléen a été poussé sur la pile après l’exécution. - Un booléen déjà sur la pile utilisé directement.
# Forme liste, condition évaluée par mogwai.assert
(a 10 ==) "a doit être égal à 10" mogwai.assert
# Forme booléenne, résultat déjà sur la pile
a 0 > "a doit être positif" mogwai.assert
a ->type .list == "a doit être une liste" mogwai.assert
Le message est affiché avec l’erreur, c’est une information de diagnostic, pas une valeur. Il n’est pas accessible programmatiquement via error.last, qui renvoie MW.9.
Le cas d’usage évident : les préconditions dans les fonctions
to 'divide' with [x: .number y: .number] do
{
(y 0 !=) "le diviseur ne doit pas être zéro" mogwai.assert
x y /
}
C’est le pattern pour lequel mogwai.assert a été conçu : protéger le point d’entrée d’une fonction, échouer immédiatement et clairement si le contrat est violé.
Il peut aussi servir pour des tests en script, pour valider des hypothèses sur l’état à un moment donné de l’exécution, sans avoir besoin d’un framework de test externe.
Et maintenant ?
La v8.6 est disponible dès maintenant sur NuGet et GitHub.
La documentation complète a été mise à jour. Exemples, changelog et playground sont disponibles sur mogwai.eu.com.
Si vous découvrez MOGWAI — un moteur de scripting RPN basé sur une pile pour .NET, open-sourcé sous licence Apache 2.0 — le README est le meilleur point de départ.
Vos retours sont les bienvenus dans les Discussions.