Paradigmes
Dans cette séance, nous examinons différentes catégories de langages de programmation et illustrons les caractéristiques qui en découlent avec des exemples de code. Nous apprendrons la différence entre les langages impératifs et déclaratifs, la pertinence d’un système de types, l’encapsulation, et pourquoi la récursion est un élément fondamental de la programmation fonctionnelle.
Résumé de la séance
Les langages de programmation sont généralement inspirés par un ensemble de paradigmes. Tous les problèmes de programmation ne correspondent pas également à tous les paradigmes. Par conséquent, il est crucial de connaître les paradigmes de base, leurs possibilités et leurs limites afin de choisir le langage de programmation approprié.
Définition d’un paradigme
Un paradigme de programmation est une façon relativement abstraite de conceptualiser et de structurer l’implémentation d’un programme informatique. Un langage de programmation peut être classé comme supportant un ou plusieurs paradigmes.
-- Wikipedia
Dans cette unité de cours, nous examinerons deux principales catégories de paradigmes et leurs diverses sous-catégories.
- Impératif : Comment atteindre un objectif, instructions concrètes étape par étape
- Déclaratif : Ce qui est attendu, en termes d’entrées et de sorties
Paradigmes impératifs
- Il s’agit de notre premier paradigme global, c’est-à-dire que les autres paradigmes en sont des formes spécialisées
- Essentiellement, la programmation impérative consiste à dire à l’ordinateur comment faire quelque chose, étape par
étape.
- Comment : nous fournissons des instructions concrètes, indiquant comment résoudre un problème.
- Étape par étape : les instructions sont listées les unes après les autres. Nous les traitons séquentiellement.
Langages impératifs primitifs
Les langages de programmation les plus anciens et les plus simples appartiennent à cette catégorie, et les instructions sont souvent orientées vers le matériel sous-jacent.
Assembleur
- Exemple en assembleur :
section .data
hello: db 'Hello, World!',10 ; 'Hello, World!' plus a linefeed character
helloLen: equ $-hello ; Length of the 'Hello world!' string
section .text
global _start
_start:
mov eax,4 ; The system call for write (sys_write)
mov ebx,1 ; File descriptor 1 - standard output
mov ecx,hello ; Put the offset of hello in ecx
mov edx,helloLen ; helloLen is a constant, so we don't need to say
; mov edx,[helloLen] to get it's actual value
int 80h ; Call the kernel
mov eax,1 ; The system call for exit (sys_exit)
mov ebx,0 ; Exit with return "code" of 0 (no error)
int 80h;
Niveau assez bas. Il faut littéralement connaître les abréviations
eax,ebx, ... pour appeler un simple "système"outet afficher quelque chose à l’écran. C’est parce que vous écrivez l’équivalent lisible par l’humain du bytecode machine.
Basic
Basic nous fournit au moins une fonction print à appeler. Il existe toutefois d’autres concepts étranges, comme sauter
dans le code source avec des instructions GOTO.
Vous pouvez expérimenter avec des programmes Basic sur "https://www.quitebasic.com"
Procédural
- Une première constatation a été que les programmes longs et complexes sont difficiles à écrire, et encore plus difficiles à comprendre.
- D’où l’idée d’encapsuler la logique répétitive dans des fonctions.
- Le code est toujours exécuté ligne par ligne, mais
- L’exécution du code saute à l’intérieur et à l’extérieur des fonctions.
ANSI C
Un langage bas niveau répandu qui supporte les fonctions est le langage de programmation C :
#include <stdio.h>
// Function declaration
int sumToN(int n);
int main() {
int n = 100;
int sum = sumToN(n);
printf("The sum of numbers from 1 to %d is %d\n", n, sum);
return 0;
}
// Function definition
int sumToN(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
C est un langage compilé, donc pour l’exécuter, nous devons le convertir en code machine :
- Compiler avec :
$ gcc sum.c - Cela crée un nouveau fichier
a.out, un binaire en code machine. Nous pouvons simplement l’exécuter :
sum.c
Pourquoi le compilateur est-il nécessaire ? L’assembleur n’avait pas besoin de compilateur.
Le code assembleur est littéralement l’équivalent lisible par l’humain des instructions machine (bytecode). L’exécution d’un compilateur consiste à traduire chaque instruction individuelle en bytecode.
C, en revanche, n’est pas une version directe un-à-un du bytecode. Il est plus facile à comprendre pour nous, humains, mais pour que l’ordinateur le comprenne, nous devons quand même le convertir en bytecode.
Cela présente aussi quelques avantages :
* Optimisations de performance.
* Détection des erreurs bas niveau, par exemple erreurs de syntaxe ou variables non référencées.
Note : C présente encore de nombreuses subtilités, par exemple rien n’empêche de dépasser les limites d’un tableau :
#include <stdio.h>
int main() {
int numbers[] = {1, 2, 3, 4, 5}; // Array of 5 integers
int sum = 0;
int size = sizeof(numbers) / sizeof(numbers[0]); // Compute array length
// Loop over the array
for (int i = 0; i < size; i++) {
sum += numbers[i]; // Access each element
}
printf("The sum of the array elements is %d\n", sum);
return 0;
}
Voyons ce qui se passe :
Que s’est-il passé ?
Le compilateur C ne vérifie pas que les dimensions des tableaux sont respectées. Notre boucle vient de dépasser les limites du tableau et a ajouté n’importe quelle valeur présente dans la RAM à cet endroit.
C ne nous protège pas contre les erreurs de programmation stupides, nous obtenons simplement des résultats incohérents.
Bash
- Bash est un autre exemple utile de langage procédural.
- Contrairement à C, il ne nécessite pas de compilateur, seulement un interpréteur.
- Vous avez déjà utilisé l’interpréteur Bash : c’est votre ligne de commande !
- Bash est très pratique, car il combine la puissance d’accès au système de fichiers avec la programmation déclarative.
- Créer des répertoires et fichiers
- Supprimer, renommer des répertoires et fichiers
- Expressions régulières
- Tableaux
- Fonctions
- ... et la possibilité d’appeler n’importe quel programme que vous avez installé (récupérer des pages web, extraire des informations, envoyer des messages à des webAPIs, ...)
Voici un exemple de programme qui crée 100 dossiers, chacun contenant un fichier numéroté identique à l’intérieur :
Orienté Objet
- Avec
C, nous avons déjà vu comment la fonctionnalité peut être encapsulée. - Mais souvent, on ne veut pas seulement modulariser la logique, on veut modulariser les données.
- Les langages orientés objet ajoutent le concept de classes et de masquage de l’information :
- Une classe définit
Définition macro OO ici. Secrets de la classe / encapsulation, orientation sur des objets du monde réel. Combinaison de l’état et du comportement.
Typage strict, encapsulation stricte
Idéalement, en utilisant un langage de programmation orienté objet, nous devons respecter les règles OO.
- Les classes définissent strictement quelles entrées et sorties elles acceptent
- Idéalement, un compilateur nous avertit avant l’exécution si nous violons les types.
- Les objets disposent d’un mécanisme approprié pour protéger leurs secrets
- Nous ne pouvons pas accéder directement aux secrets de la classe, nous devons passer par les mécanismes d’accès prévus.
- Idéalement, un compilateur nous avertit lorsque nous essayons d’accéder à quelque chose de restreint.
Java possède des mécanismes pour faire respecter l’encapsulation, mais par défaut, ils ne sont pas activés.
Voici un exemple d’une classe Java représentant une Voiture avec un accès ouvert à toutes les variables :
public class Car {
public int remainingFuel = 50;
public int mileage = 200;
public int horsepower = 80;
public int speed = 0;
public void accelerate() {
speed = speed + 10;
System.out.println("Vroooooom");
}
public void slowDown() {
speed = speed - 10;
System.out.println("Screeeeech");
}
public void fuelUp() {
remainingFuel = 100;
System.out.println("Glug, glug, glug");
}
}
Pas typé strictement, encapsulation non stricte
- Python est un bon exemple de langage qui gère les choses avec beaucoup plus de souplesse que Java.
- Python supporte les types et les classes, donc on pourrait le considérer comme un langage OO à part entière.
Considérons cet exemple :
- Nous avons une classe
Car - La classe Car possède un champ
fuel(int) fuelest privé (notation__)
class Car:
# Constructor for car, here we initialize fuel with the provided int
def __init__(self, fuel: int):
self.__fuel = fuel # "private" by convention
# Driving consumes some fuel
def drive(self, distance: int):
"""Consumes 1 unit of fuel per unit of distance"""
if self.__fuel >= distance:
self.__fuel -= distance
print(f"Drove {distance} units, fuel left: {self.__fuel}")
else:
print("Not enough fuel!")
# Getter to look up remaining fuel
def get_fuel(self) -> int:
return self.__fuel
Et pendant un certain temps, cela semble être un langage de programmation très raisonnable :
- Nous pouvons créer des objets
Car. - Nous pouvons utiliser la méthode
get_fuelpour vérifier combien de carburant reste. - Nous pouvons conduire, en consommant du carburant. La méthode ne nous permettra pas de conduire s’il n’y a pas assez de carburant restant.
from car import Car
## Here we initialize a Car object with 10 units of fuel.
my_car = Car(10)
print("Fuel (via method):", my_car.get_fuel())
# Normal drive:
my_car.drive(3)
# Let's check how much fuel is remaining:
print("Fuel (via method):", my_car.get_fuel())
Cependant, il s’avère que ce langage n’est pas sûr du tout :
- Nous pouvons contourner nous-mêmes le champ
fuelet y mettre n’importe quoi. - Les types sont purement décoratifs, rien ne nous empêche d’écrire autre chose.
- Comme il n’y a pas de compilateur, nous ne découvrons nos erreurs de programmation (la plupart du temps involontaires) qu’une fois qu’il est trop tard (à l’exécution du programme).
## So far everything was normal, but we can totally abuse the provided code and deviate from string OO programming.
# 1) We can "steal" fuel from the car, without driving:
# (fuel is a private field, but python lets as tamper anyway)
my_car._Car__fuel = 1 # whoops, we just dumped fuel.
print("Fuel after external tampering:", my_car.get_fuel())
# 2) Even worse, although "fuel" is supposed to be an int, we can write a string inside:
my_car._Car__fuel = "infinite" # no runtime type check
print("Fuel is now:", my_car._Car__fuel)
# This breaks logically but Python won't stop you
# Now fuel is no longer an int but a string, what will happen if we attempt to drive ?
my_car.drive(2) # would throw a TypeError at runtime => The program will crash AT RUNTIME !
Le code ci-dessus n’est pas sécurisé
Un développeur peut toujours accéder au champ "privé" balance, par exemple en utilisant my_car._Car__fuel = "infinite". Il n’y a même pas de vérification de type !
Pourquoi cela ?
- Le double underscore est purement décoratif, exprimant le mince espoir que tout le monde respecte les internes des classes.
- Le double underscore ne fournit absolument aucune protection au niveau du langage contre quelqu’un qui manipule les internes de la classe. Il est surtout décoratif.
Mon avis
J’espère ne jamais me retrouver à bord d’un avion dont le constructeur aurait décidé d’implémenter des composants essentiels en Python.
Paradigmes déclaratifs
Les langages déclaratifs sont un autre terme générique, avec de nombreux partisans. Essentiellement, les langages déclaratifs s’intéressent moins au comment atteindre un résultat et se concentrent plutôt sur ce qui est souhaité.
Graphique
Il est très probable que vous ayez déjà travaillé avec un langage déclaratif : HTML
En HTML, vous décrivez uniquement ce que vous attendez de voir. Vous ne vous souciez pas de la façon dont le navigateur traduit votre description en résultat graphique à un niveau technique.
<html>
<body>
<h1>I want a big fat heading.</h1>
<h2>And then I want some subheading</h2>
<p>... and then I want some text.</p><br/>
<p>and then a cute image:
<p>
<img src="https://upload.wikimedia.org/wikipedia/commons/6/6e/Golde33443.jpg"/>
</body>
</html>
Une idée pour un autre langage graphique ?
LaTeX ! Vous décrivez uniquement le contenu du document, LaTeX s’occupe du reste !
Voici un exemple de code LaTeX public en action, générant un document.
Données
Récupérer des données depuis une base de données nécessite souvent beaucoup d’optimisation. En tant qu’utilisateur, vous vous souciez rarement de la façon exacte dont les données sont récupérées, tant que cela reste ultra-rapide.
Les langages de requête de bases de données comme Sequel (SQL) servent justement à cela : vous décrivez ce que vous voulez, pas comment l’obtenir efficacement.
Par exemple, pour une table de base de données contenant des professeurs :
| Prénom | Nom | Champs |
|---|---|---|
| Alan | Turing | Theoretical CS, AI |
| John | McCarthy | AI, Lisp |
| Donald | Knuth | Algorithms, TeX |
| Edsger | Dijkstra | Algorithms, Software Eng |
| Grace | Hopper | Programming Languages |
| Tim | Berners-Lee | Web, Networking |
| Ada | Lovelace | Programming, Computation |
| Marvin | Minsky | AI, Robotics |
| Claude | Shannon | Information Theory |
| Leslie | Lamport | Distributed Systems |
| John | Backus | Programming Languages, Fortran |
| Richard | Stallman | Free Software, OS |
| Ken | Thompson | Operating Systems |
| Dennis | Ritchie | Programming Languages, C |
| Barbara | Liskov | Distributed Systems, OOP |
| Vinton | Cerf | Networking, Internet |
| Donald | Becker | OS, Networking |
| Andrew | Tanenbaum | OS, Networking |
| Bjarne | Stroustrup | Programming Languages, C++ |
| Shafi | Goldwasser | Cryptography |
Nous pourrions récupérer tous les professeurs ayant un "m" dans leur prénom :
Pourquoi l’instruction SQL est-elle déclarative ?
Parce que nous ne spécifions pas COMMENT la base de données doit être recherchée. Nous décrivons seulement ce que nous voulons.
Fonctionnel
"[...] une approche fonctionnelle consiste à composer le problème comme un ensemble de fonctions à exécuter. Vous définissez soigneusement l’entrée de chaque fonction, et ce que chaque fonction
retourne - MSDN"
- est une approche déclarative : le programmeur définit quoi exécuter, mais pas dans quel ordre.
- Le programme n’est pas exécuté ligne par ligne, mais appel de fonction par appel de fonction.
- pas de boucles comme structures de contrôle.
- usage intensif de la récursion.
Récursion (récapitulatif)
Dans les langages fonctionnels purs, vous ne pouvez pas programmer de manière impérative :
- Vous ne pouvez pas utiliser des incréments basiques comme
i = i+1 - À la place, vous devez utiliser la récursion.
Voici un petit récapitulatif de la façon dont les boucles peuvent être transformées en récursions :
Les solutions itératives (basées sur des boucles) et récursives sont également puissantes, c’est-à-dire que chaque solution itérative peut être transformée en solution récursive et vice-versa.
Calcul itératif de la factorielle :
private static int facultyLoop(int n) {
int faculty = 1;
int i = 1;
while (i <= n) {
faculty = faculty * i;
i++;
}
return faculty;
}
Calcul récursif de la factorielle :
La programmation fonctionnelle est-elle meilleure que la programmation impérative ?
Le choix entre résoudre un problème de manière itérative ou récursive dépend entièrement du problème. Certains problèmes sont décrits plus élégamment comme une fonction récursive et deviennent trop verbeux lorsqu’ils sont codés de manière itérative. Pour d’autres, c’est l’inverse. Il n’y a pas de gagnant clair, tout dépend de la nature du problème que vous résolvez. Cependant, pour une véritable programmation fonctionnelle, un langage OO comme Java n’est pas le meilleur choix. Chaque appel récursif ajoute une fonction à la pile, alors que d’autres langages comme Haskell disposent de meilleurs mécanismes pour gérer les effets secondaires de la récursion.
Haskell
Haskell est considéré comme un langage fonctionnel pur (pas de boucles).
Voici un petit exemple de la façon de réaliser une tâche mathématique simple en utilisant le langage de programmation Haskell :
- Chaque bloc de deux lignes définit d’abord une fonction (types des entrées et sorties)
- La ligne suivante définit ensuite comment le résultat est calculé, en fonction des entrées.
-- Function to get all proper divisors of n
divisors :: Int -> [Int]
divisors n = [x | x <- [1 .. n `div` 2], n `mod` x == 0]
-- Function to check if n is perfect
sumDivisors :: Int -> Bool
sumDivisors n = sum (divisors n) == n
-- Result consumes the number as input
isPerfect :: Int -> Bool
isPerfect n = sumDivisors n
Pour exécuter le programme, nous avons besoin de l’interpréteur correspondant (il est aussi possible de compiler Haskell, mais afficher les résultats devient alors un peu plus compliqué).
$ ghci perfect.hs
GHCi, version 9.12.2: https://www.haskell.org/ghc/ :? for help
[1 of 2] Compiling Main ( perfect.hs, interpreted )
Ok, one module loaded.
L’interpréteur a maintenant chargé notre fichier Haskell, nous pouvons simplement appeler les fonctions.
Les langages fonctionnels sont bien adaptés aux fonctions, c’est-à-dire aux problèmes mathématiques. Cependant, pour des problèmes du monde réel, ils ont tendance à causer beaucoup de maux de tête. Un programme qui se contente d’afficher des résultats (sans utiliser l’interpréteur) nécessite déjà des concepts de programmation avancés.
Piloté par les événements
Les langages pilotés par les événements suivent des règles simples du type « si ceci se produit, alors déclencher cette action ».
Les langages graphiques, ou ceux qui supportent les interfaces utilisateur, ont souvent un composant piloté par les événements pour réagir aux interactions de l’utilisateur.
JavaScript en est un exemple (gestion des clics de souris, frappes au clavier, etc.).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Button Alert Example</title>
</head>
<body>
<!-- Event example: Click is an event, the action to invoke is the showAlert function. -->
<button onclick="showAlert()">Click Me</button>
<script>
function showAlert() {
alert("Button was clicked!");
}
</script>
</body>
</html>
Récapitulatif
| Paradigme | Focus | Utilisations courantes | Langages |
|---|---|---|---|
| Impératif | Instructions étape par étape | Scripts simples, bas niveau | Assembleur, Basic |
| Procédural | Organisation en fonctions | Applications simples, enseignement | C, Python, Bash |
| POO | Modélisation d’entités réelles | Grandes applications, interfaces graphiques | C++, Java |
| Concurrent | Plusieurs tâches simultanées | Systèmes temps réel, tâches parallèles | Go, Erlang |
| Déclaratif | Décrire le résultat, pas les étapes | SQL, configurations, UI | SQL, HTML, CSS |
| Fonctionnel | Fonctions pures, immuabilité | Problèmes mathématiques | Clojure, Haskell |
| Piloté par événements | Gestion des événements | Interfaces graphiques, applications web | JavaScript, C# (pour UI) |
Arbre Impératif vs Déclaratif
graph TD
A[Programming Paradigms]
%% Root categories
A --> B[Imperative]
A --> C[Declarative]
%% Imperative descendants
B --> B1[Procedural]
B1 --> B1a[Assembly]
B1 --> B1b[C]
B --> B2[Object-Oriented]
B2 --> B2b[Java]
B2 --> B2c[C++]
B --> B3[Concurrent/Parallel]
B3 --> B3a[Go]
B3 --> B3b[Erlang]
%% Declarative descendants
C --> C1[Functional]
C1 --> C1a[Haskell]
C1 --> C1b[Lisp]
C --> C2[Graphic]
C2 --> C2a[HTML]
Langages multi-paradigmes
Certains langages de programmation présentent des chevauchements significatifs de paradigmes. On les appelle aussi "langages multi-paradigmes".
Incompatibilités de paradigmes
Nous avons déjà vu un exemple fort d’incompatibilité de paradigmes : coder en OO avec Python.
- Le fait qu’une chose puisse être implémentée dans un langage donné ne signifie pas qu’elle devrait l’être, ni que c’est un bon style de programmation.
- En fait, il est parfaitement possible de forcer des constructions de programmation dans un langage pour lequel elles
n’ont pas été conçues.
- Dans le meilleur des cas, le code devient trop complexe.
- Dans le pire des cas, le code ne fait pas du tout ce que l’on pourrait attendre...
Utiliser le bon outil pour le bon usage
Quand votre code devient trop compliqué et que vous avez constamment l’impression que le langage lui-même vous limite dans ce que vous voulez faire, il y a de fortes chances que vous soyez confronté à une incompatibilité de paradigme.
OO en Python
- Lorsque vous êtes habitué aux principes OO stricts, passer à un écosystème moins OO peut entraîner des incompatibilités de paradigme : le développeur essaie d’imposer un paradigme à un langage qui n’a pas été conçu principalement pour cela.
- Cela arrive à la plupart des développeurs Java venant d’un background Java.
- Java permet facilement des principes OO comme le masquage de l’information, les champs privés, etc.
- Comme vu précédemment, en Python, les champs peuvent être déclarés « pseudo » privés, mais cela ne change pas réellement leur comportement et ils restent accessibles.
Compilé versus interprété
- Tout au long de la séance, nous avons vu différents langages compilés et différents langages interprétés.
- Notez que cette distinction ne s’aligne pas avec la dichotomie
Déclaratif/Impératif.- Certains langages supportent même les deux : le code Haskell peut être interprété et compilé.
- Souvent, les langages compilés ont un système de types plus strict, simplement parce que l’on peut imposer la sécurité des types à la compilation et éliminer toute une catégorie d’erreurs de programmation.
Langages compilés
- Système de types plus strict
- Optimisations avant l’exécution
- Le bytecode est habituellement lié à une plateforme spécifique
Pourquoi habituellement ?
Parce que l’on peut avoir les deux : un compilateur et un interpréteur. Java en est un exemple : le code est compilé, mais doit encore être interprété par une JVM.
Interprétés
- Souvent des scripts
- Souvent une syntaxe plus simple
- Souvent pas de système de types strict (ou pas de types du tout)
Langages ésotériques
En guise de conclusion, les paradigmes ne sont pas toujours pragmatiques. La communauté des langages de programmation a parfois tendance à inventer des paradigmes absurdes, juste pour démontrer des concepts et introduire un nouveau langage.
Voici quelques-uns de mes préférés :
Whitespace
- Whitespace n’a que deux caractères :
- Espace
- Tabulation
- Avec cela, vous avez des représentations de 0 et de 1, donc vous pouvez écrire tout ce que vous voulez en binaire.
- Voici un programme "Hello, World!" en Whitespace :

Crédit image : Wikipedia
Vous ne voyez rien ? Moi non plus, c’est le but :)
Une astuce académique
Si votre professeur ne précise pas dans quel langage vous devez faire votre devoir, rendez une feuille vide en précisant que vous l’avez implémenté en Whitespace et que vous avez imprimé la solution pour leur commodité.
Ook!
Ook! est un autre langage sophistiqué. Techniquement, ce n’est qu’une variante du langage Brainfuck (un langage conçu
pour être le plus impossible à utiliser possible).
Ook!n’a que quelques commandes, et elles se ressemblent toutes, mais tout est possible.- Voici un programme Hello, World! écrit en Ook! :
Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook.
Ook! Ook. Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook?
Ook! Ook! Ook? Ook! Ook? Ook. Ook. Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook! Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook! Ook. Ook. Ook? Ook. Ook? Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook. Ook! Ook.
Ook. Ook? Ook. Ook? Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook.
Ook? Ook. Ook? Ook. Ook? Ook. Ook? Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook.
Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook. Ook. Ook? Ook. Ook? Ook. Ook. Ook! Ook.
Piet
Piet combine la programmation avec l’art moderne. Le code est une toile, et l’exécution du programme correspond à déplacer un pointeur horizontalement ou verticalement à travers la toile. Le pointeur continue dans la même direction jusqu’à ce qu’il rencontre un changement de couleur :
Voici un programme Hello, World! en Piet :

Littérature
Inspiration et lectures supplémentaires pour les esprits curieux :
- Microsoft - functional VS declarative
- Assembler online
- BASIC online compiler
- Haskell programming language
- Ook programming language
- Piet language
Voici le lien pour passer à l’unité de laboratoire : Lab 02