8.7 KiB
// Commençons par un classique
module hello;
import std.stdio;
// args n'est pas obligatoire
void main(string[] args) {
writeln("Bonjour le monde !");
}
Si vous êtes comme moi et que vous passez beaucoup trop de temps sur internet, il y a de grandes chances pour que vous ayez déjà entendu parler du D. D est un langage de programmation moderne, généraliste, multi-paradigme qui contient des fonctionnalités aussi bien de bas-niveau que haut-niveau.
D is actively developed by a large group of super-smart people and is spearheaded by Walter Bright and Andrei Alexandrescu. With all that out of the way, let's look at some examples!
D est activement développé par de nombreuses personnes très intelligents, guidées par Walter Bright)) et Andrei Alexandrescu. Après cette petite introduction, jetons un coup d'oeil à quelques exemples.
import std.stdio;
void main() {
//Les conditions et les boucles sont classiques.
for(int i = 0; i < 10000; i++) {
writeln(i);
}
// On peut utiliser auto pour inférer automatiquement le
// type d'une variable.
auto n = 1;
// On peut faciliter la lecture des valeurs numériques
// en y insérant des `_`.
while(n < 10_000) {
n += n;
}
do {
n -= (n / 2);
} while(n > 0);
// For et while sont très utiles, mais en D, on préfère foreach.
// Les deux points : '..', créent un intervalle continue de valeurs
// incluant la première mais excluant la dernière.
foreach(i; 1..1_000_000) {
if(n % 2 == 0)
writeln(i);
}
// On peut également utiliser foreach_reverse pour itérer à l'envers.
foreach_reverse(i; 1..int.max) {
if(n % 2 == 1) {
writeln(i);
} else {
writeln("Non !");
}
}
}
We can define new types with struct
, class
, union
, and enum
. Structs and unions
are passed to functions by value (i.e. copied) and classes are passed by reference. Futhermore,
we can use templates to parameterize all of these on both types and values!
On peut définir de nouveaux types avec les mots-clés struct
, class
,
union
et enum
. Les structures et les unions sont passées au fonctions par
valeurs (elle sont copiées) et les classes sont passées par référence. De plus,
On peut utiliser les templates pour rendre toutes ces abstractions génériques.
// Ici, 'T' est un paramètre de type. Il est similaire au <T> de C++/C#/Java.
struct LinkedList(T) {
T data = null;
// Utilisez '!' pour instancier un type paramétré.
// Encore une fois semblable à '<T>'
LinkedList!(T)* next;
}
class BinTree(T) {
T data = null;
// Si il n'y a qu'un seul paramètre de template,
// on peut s'abstenir de parenthèses.
BinTree!T left;
BinTree!T right;
}
enum Day {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}
// Utilisez alias pour créer des abreviations pour les types.
alias IntList = LinkedList!int;
alias NumTree = BinTree!double;
// On peut tout aussi bien créer des templates de function !
T max(T)(T a, T b) {
if(a < b)
return b;
return a;
}
// On peut utiliser le mot-clé ref pour s'assurer que quelque chose est passé
// par référence, et ceci, même si a et b sont d'ordinaire passés par valeur.
// Ici ils seront toujours passés par référence à 'swap()'.
void swap(T)(ref T a, ref T b) {
auto temp = a;
a = b;
b = temp;
}
// Avec les templates, on peut également passer des valeurs en paramètres.
class Matrix(uint m, uint n, T = int) {
T[m] rows;
T[n] columns;
}
auto mat = new Matrix!(3, 3); // T est 'int' par défaut
À propos de classes, parlons des propriétés. Une propriété est, en gros,
une méthode qui peut se comporter comme une lvalue. On peut donc utiliser
la syntaxe des structures classiques (struct.x = 7
) comme si il
s'agissait de méthodes getter ou setter.
// Considérons une classe paramétrée avec les types 'T' et 'U'
class MyClass(T, U) {
T _data;
U _other;
}
// Et des méthodes "getter" et "setter" comme suit:
class MyClass(T, U) {
T _data;
U _other;
// Les constructeurs s'apellent toujours 'this'.
this(T t, U u) {
// Ceci va appeller les setters ci-dessous.
data = t;
other = u;
}
// getters
@property T data() {
return _data;
}
@property U other() {
return _other;
}
// setters
@property void data(T t) {
_data = t;
}
@property void other(U u) {
_other = u;
}
}
// Et on l'utilise de cette façon:
void main() {
auto mc = new MyClass!(int, string)(7, "seven");
// Importer le module 'stdio' de la bibliothèque standard permet
// d'écrire dans la console (les imports peuvent être locaux à une portée)
import std.stdio;
// On appelle les getters pour obtenir les valeurs.
writefln("Earlier: data = %d, str = %s", mc.data, mc.other);
// On appelle les setter pour assigner de nouvelles valeurs.
mc.data = 8;
mc.other = "eight";
// On appelle les setter pour obtenir les nouvelles valeurs.
writefln("Later: data = %d, str = %s", mc.data, mc.other);
}
With properties, we can add any amount of logic to our getter and setter methods, and keep the clean syntax of accessing members directly!
Other object-oriented goodies at our disposal
include interface
s, abstract class
es,
and override
ing methods. D does inheritance just like Java:
Extend one class, implement as many interfaces as you please.
We've seen D's OOP facilities, but let's switch gears. D offers
functional programming with first-class functions, pure
functions, and immutable data. In addition, all of your favorite
functional algorithms (map, filter, reduce and friends) can be
found in the wonderful std.algorithm
module!
Avec les propriétés, on peut constuire nos setters et nos getters comme on le souhaite, tout en gardant un syntaxe très propre, comme si on accédait directement à des membres de la classe.
Les autres fonctionnalités orientées objets à notre disposition incluent les interfaces, les classes abstraites, et la surcharge de méthodes. D gère l'héritage comme Java: On ne peut hériter que d'une seule classe et implémenter autant d'interface que voulu.
Nous venons d'explorer les fonctionnalités objet du D, mais changeons
un peu de domaine. D permet la programmation fonctionelle, avec les fonctions
de premier ordre, les fonctions pure
et les données immuables.
De plus, tout vos algorithmes fonctionelles favoris (map, reduce, filter)
sont disponibles dans le module std.algorithm
.
import std.algorithm : map, filter, reduce;
import std.range : iota; // construit un intervalle excluant la dernière valeur.
void main() {
// On veut un algorithm qui affiche la somme de la listes des carrés
// des entiers paires de 1 à 100. Un jeu d'enfant !
// On se content de passer des expressions lambda en paramètre à des templates.
// On peut fournier au template n'importe quelle fonction, mais dans notre
// cas, les lambdas sont pratiques.
auto num = iota(1, 101).filter!(x => x % 2 == 0)
.map!(y => y ^^ 2)
.reduce!((a, b) => a + b);
writeln(num);
}
Vous voyez comme on a calculé num
comme on le ferait en haskell par exemple ?
C'est grâce à une innvoation de D qu'on appelle "Uniform Function Call Syntax".
Avec l'UFCS, on peut choisir d'écrire un appelle à une fonction de manière
classique, ou comme un appelle à une méthode. Walter Brighter a écrit un
article en anglais sur l'UFCS ici.
Pour faire court, on peut appeller une fonction dont le premier paramètre
est de type A, comme si c'était une méthode de A.
J'aime le parallélisme. Vous aimez les parallélisme ? Bien sur que vous aimez ça Voyons comment on le fait en D !
import std.stdio;
import std.parallelism : parallel;
import std.math : sqrt;
void main() {
// On veut calculer la racine carré de tous les nombres
// dans notre tableau, et profiter de tous les coeurs
// à notre disposition.
auto arr = new double[1_000_000];
// On utilise un index et une référence à chaque élément du tableau.
// On appelle juste la fonction parallel sur notre tableau !
foreach(i, ref elem; parallel(arr)) {
ref = sqrt(i + 1.0);
}
}