Site Perso de

Thomas JANNAUD

Des actualités personnelles sous un style impersonnel, et inversement.



Tutoriel CamL
Bases, astuces et codes sources pour se familiariser avec ce language 30 Mars 2008
ocaml

Vous pouvez vous procurer CamL sur le site de l'INRIA, qui a inventé ce langage qui appartient à la famille des langages fonctionnels (par exemple, ce n'est pas un langage objet). Cela signifie que les boucles for ... pourraient très bien ne pas exister dans ce langage car on pourrait les remplacer, par exemple, par des fonctions récursives avec un test à l'entrée.

Il a été écrit, à la base, pour faciliter la création de compilateurs. C'est un langage très poussé, mais très très propre : c'est très agréable de coder avec, même si les débuts sont parfois difficiles. En effet, le compilateur est extrêmement exigeant. Cela veut aussi dire que si le compilateur compile, il y a de grandes chances pour que le programme n'ait pas d'erreurs. Et ça, c'est bien.

Pour ces raisons, le langage CamL est sans doute celui que je préfère. Je parle du langage en lui-même, pas de l'environnement dans lequel il s'exécute, bien entendu (une console). Mon rêve serait de pouvoir utiliser ce langage dans un environnement de type Visual Basic.

Vous trouverez dans cet ouvrage de quoi apprendre caml par l'exemple... sans doute le meilleur moyen pour appréhender ce langage.
D'autre part, par expérience, vous verrez qu'un site internet ne remplacera jamais un livre papier.
Cette version est en français.

Conseils

Je ne saurais aussi que trop vous conseiller d'aller voir ma page de codes sources, où je mets pas mal de codes sources en ligne, ce qui peut être très utile pour avoir une vision d'ensemble de la physionomie d'un programme en CamL. En effet, c'est une approche totalement différente de la programmation habituelle, où passer des fonctions en paramètre est chose commune, pour un code clair et compact au final.

D'autre part, au début on ne comprend jamais où mettre les points-virgules dans le code (parfois 0, parfois 1, parfois 2 à la suite). Regarder des programmes sans essayer de comprendre ce qu'ils font peut vous aider à assimiler cette notion difficile.
Voici par ailleurs un excellent tutoriel pour débuter doucement en CamL.

Utilisation du compilateur

Il y a deux manières d'utiliser CamL (ou OCamL) : écrire des fonctions dans l'interpréteur, une par une, sans vraiment avoir tout le code sous ses yeux, ou bien écrire son code dans un document texte, l'enregistrer, pouvoir éditer et compiler. Beaucoup d'éditeurs sont justement faits spécialement pour ce genre de chose (coloration syntaxique, compilation et exécution du code de manière simple, ...).

Je vous conseille (roulement de tambour) la seconde solution ! Préférez SublimeText, Atom, Emacs, Notepad++, EditPlus... à un éditeur de texte basique. Ce genre d'éditeurs foisonne sur le Net. Voici les lignes de commandes utilisées pour la compilation et l'exécution :


compilation : ($(FileName) et $(FileDir) sont propres à EditPlus, mais j'imagine que tous les éditeurs ont bien leur petit équivalent
Command="C:\\Program Files\\Objective Caml\\Bin\\ocamlc.exe"
Argument=-I "c:\\program files\\objective caml\\lib" -o output.exe $(FileName)
InitDir=$(FileDir)

exécution :
Command="c:\\program files\\objective caml\\bin\\ocamlrun.exe"
Argument="$(FileDir)\\output.exe"
InitDir=$(FileDir)

Remarque pour la ligne 'Argument' de l'exécution : on peut écrire aussi cela
Argument="$(FileDir)\\output.exe" < input.txt

De cette manière, si le programme demande à l'utilisateur de rentrer un nombre, puis une ligne de caractères, puis... et bien il ira chercher ces renseignements dans le fichier "input.txt" (dans le même dossier que notre code source) plutôt que de demander une saisie dans la console. C'est particulièrement pratique dans le cas où l'on veut tester notre programme sur une grosse entrée (exemple : une matrice, un graphe, un labyrinthe, ...) pour pas avoir à tout retaper à chaque fois.

Plusieurs choses à savoir avant de commencer :

Structures de contrôle

la fonction main :
Il n'y en a pas : vous écrivez vos fonctions, ... et CamL exécutera le code qui est hors des fonctions. Ainsi il est courant d'écrire toutes ses fonctions, et ensuite quelque chose du genre "demande un entier n à l'utilisateur", puis affiche f(n).
Déclaration d'une variable constante :
let x = 3 in
...
ou
let x = 3 and y = 4 and z = 2 in
...
ou
let x = 3;;
...
pour les variables globales (il faut alors les mettre en tout début du fichier)

Attention !!!! on ne pourra jamais modifier la valeur d'une variable ainsi déclarée : ce sont des constantes. Cela peut paraître bizarre que la déclaration par défaut soit la constante, mais on se rend vite compte que c'est l'essentiel d'un code au final.

Déclaration d'une variable qui peut changer
let x = ref 3 in
incr x; (* incrémentation de notre variable ; decr pour décrémenter *)
x := !x * 2;
(* on a fait (3 + 1) * 2 *)

Remarquez les symboles d'affectation := et de "récupération de la valeur" !.Vous l'avez donc compris, en faisant ref, on déclare en fait une sorte de pointeur vers une variable. CamL repère grâce à ces signes si l'on ne cherche pas sans faire exprès à affecter une nouvelle valeur à une constante, par exemple. Comme on peut même passer un pointeur en argument d'une fonction, cela permet d'être sur que l'on ne fera pas n'importe quoi avec, en particulier à faire pointer notre pointeur vers une autre variable que celle originelle, puisque le pointeur est déclaré comme un pointeur CONSTANT vers une référence.

boucle for
for k = debut to fin do
...;
...
done;
Il n'y a pas possibilité de faire aller k de 2 en 2 directement, c'est à nous d'utiliser 2*k. On peut quand même faire for k = fin downto debut do si l'on veut que les valeurs aillent décroissant.
boucle while
while condition do (* pas besoin de mettre de parenthèses autour de la condition *)
...
done;
condition :
Si on n'a qu'une seule ligne à écrire et que l'on veut condenser :
if condition then ...;
ou bien
if condition then
...;
ou bien (cas de plusieurs choses à faire)
if condition then (
...;
...;
...); (* La parenthèse tient ici lieu d'accolade en C++ ou en Java *)
Le type float
CamL distingue les entiers des flottants : on ne peut pas (je crois) ajouter/multiplier/... a à b si a est un float et b un int. Les opérateurs arithmétiques pour les float sont ceux des int suivis d'un point : 3.14 *. 2.67 par exemple. Attention, si vous faites 'let a = 1 in', a est un int. Si vous le voulez float faites let a = 1. in>. int_of_float ou float_of_int pour passer d'un int à un float.
Déclaration d'une fonction
let [rec] f x y z = (*si la fonction est destinée à être récursive *)
  x + y + z;
  print_newline();;

Le programme voit qu'à x+y+z il n'y a pas vraiment d'instruction, juste une valeur donnée en vrac. Il la prend en tant que telle comme valeur de retour de la fonction. Il n'y a donc pas de mot clé return ni rien d'autre. Par conséquent la ligne print_newline() ne sera jamais exécutée.

Attention ! Bien que vous voyez que l'on ne donne pas de type à notre fonction, il faut absolument dans tous les cas retourner toujours la même chose. Ceci est par conséquent interdit :

let f x =
  if x > 2 then
  (* CamL comprend ici que x doit être un entier et à la ligne d'après
  que la fonction renvoie un entier, donc f: int -> int *)
    3
  else
     "toto";; (* erreur de compilation : on doit renvoyer un int, pas une chaîne *)

Remarque : si l'on a effectivement besoin d'une fonction qui renvoie dans un cas un entier, et dans un autre une chaîne, il y a 2 moyens pour arriver à ses fins.

  • Le premier : renvoyer un triplet (bool, int, string), où le booléen est à true si l'on renvoie un entier, et à false sinon. Ici on renverrait (true, 3, "ce que je veux ici, a priori ça ne sera pas pris en compte") ou (false, 42, "toto") (42 étant mis au hasard puisque de même il ne devrait pas être pris en compte par la suite étant donné le booléen.
  • Autre manière de faire : Créer un nouveau type (cf les types), comme montype = Entier of int | Chaine of string, et renvoyer Entier 3 ou Chaine "toto". CamL saura que la fonction renvoie quelque chose du type montype.

Fonction ne prenant aucun argument

let f () =
  Printf.printf "bonjour, je suis une fonction ne prenant aucun argument !";;
  (* Printf.printf : affichage à l'écran d'une phrase *)

Fonction dans une fonction : idem que pour une variable

let f x y z =
  let g a = 2 * a + x in
  Printf.printf "%d %d" (g y) (g z);; (* %d demande d'afficher un entier *)

Comme vous pouvez le remarquer, un truc sympa en CamL c'est qu'il n'y a pas de parenthèses partout qui brouillent la vue et la compréhension du code

Il y a une autre manière de déclarer une fonction :
let f = fun x y z -> (ou let f = fun () -> pour les fonctions sans argument initial)
...
...;;

L'intérêt de déclarer une fonction dans une fonction n'est peut être pas très clair, surtout au vu des exemples un peu simplistes que je donne, mais dites vous que :

  1. Je ne vous ai encore pas dit comment les listes étaient implémentées en CamL
  2. On a très souvent besoin d'appliquer la même fonction à tous les éléments d'une liste
  3. la fonction 2 * a + x ne mérite pas à première vue le titre de 'fonction', il y a fort à parier que d'autres morceaux de code ne s'en serviront pas, et donc plutôt que de la déclarer globalement on peut la déclarer localement là où elle va être utilisée.
  4. D'autre part si g était globale, il faudrait déclarer g a x = ... et donc avoir 2 arguments. Ici x est connue de g.
Le fameux filtre de CamL
La fonction la plus puissante et que vous ne cesserez d'utiliser !
match x with
| 0 -> Printf.printf "bouh"
| 1 -> Printf.printf "ah, c'est un peu mieux"
| _ -> Printf.printf "bravo !"; (* on arrive ici si x vaut ni 0 ni 1 *)

Ça a encore l'air un peu pourri comme ça, mais considérez cet exemple :

match x with
| (0, a) -> Printf.printf "tu perds et on t'as mis %d buts" a
| (1, a) -> Printf.printf "ah, c'est un peu mieux"
| (b, 0) -> Printf.printf "tu lui a mis la pâtée !!"
| (b, c) -> Printf.printf "%d buts en tout" (b + c);

Ici x est un couple d'entiers, (représentant notre score puis celui de l'adversaire, mais ça le compilateur s'en ficher). Quand on fait (0, a), où a n'a encore pas été déclaré plus haut, cela veut dire que CamL doit juste filtrer si notre score est 0, et auquel cas il stocke la seconde valeur du couple dans la variable a, alors disponible pour traitement futur. Vous verrez cette fonction se révèlera indispensable dans le futur, avec les types utilisateurs.

Remarque sur les n-uplets
Si l'on fait let a = (2, "toto", 4.5), on définit un triplet. Une fonction peut renvoyer un n-uplet. Si l'on fait let a = f x et qu'on sait que f renvoie un triplet par exemple, on peut faire ensuite let (b, c, d) = a in .. et ça définit b, c et d (ou directement let (b, c, d) = f x in ... C'est assez pratique quand une fonction doit renvoyer plusieurs valeurs, sinon ça n'a guère d'intérêt.
Utilisation directe du filtre
let f = function
| 0 -> Printf.printf "bouh"
| 1 -> Printf.printf "ah, c'est un peu mieux"
| _ -> Printf.printf "bravo !"; (* on arrive ici si x vaut ni 0 ni 1 *);;
Ceci déclare la fonction f et fait directement le match avec l'argument : même pas besoin de le déclarer ; Bien sûr il faut ne pas en avoir besoin après, de l'argument (et c'est assez fréquent, vous verrez ça avec les listes).

Tableaux et matrices

let table = Array.make n 0 in...
Crée un tableau de n entiers, initialisé avec des 0
[| 1; 2; 5; 3; 42 |]
Crée un tableau quand on sait ce qu'on va mettre dedans
let table = Array.init n (fun i -> f (i)) in
Crée un tableau de n entiers, où la case d'indice i du tableau est initialisée avec f(i). Prendre let f i = Scanf.scanf " %d" (fun x -> x) in ... pour remplir le tableau initial par des entiers que l'utilisateur rentrera par la console.
let table = Array.make_matrix hauteur largeur 0 in...
Crée un tableau à deux dimensions (une matrice, quoi) initialisée avec des entiers. Il y a un autre moyen de créer une matrice : déclarer un tableau de tableaux :
let table = Array.init hauteur (fun i -> Array.make largeur 0) ou plus simplement
let table1 = Array.make largeur 0 in
let table = Array.make hauteur table1 in
for k = 1 to hauteur
  table.(k - 1) = Array.make largeur 0
done;;

Les deux premières lignes (après "simplement") servent à indiquer à CamL que l'on a un tableau de taille hauteur, et que ce tableau contient des tableaux. Le seul problème, c'est que ces tableaux sont tous les mêmes, puisqu'ils sont initialisés avec table1 (qui est un pointeur vers ...). La preuve, faites table.(0).(0) = 2; Printf.printf "%d\ " table.(1).(0) et vous verrez que ça fait 2 aussi. On demande donc dans la boucle for de recréer un certain nombre (hauteur) de nouveaux tableaux (et de nouveaux espaces mémoire). La fonction Array.make (ou init) renvoie un pointeur vers ce nouvel espace mémoire, pointeur qui est stocké dans table.(i)

Cette méthode permet de créer des tableaux de n'importe quelle dimension. D'autre part, ce n'est pas parce que je dis "matrice" que le tableau à 2 dimensions créé est rectangulaire. Non, c'est juste un tableau de tableaux, dont certains peuvent être de taille 0 ou 1 ou 10 et d'autres de taille 55. D'où les méthodes qui suivent.

Array.length table
Renvoie la taille d'un tableau. Si c'est une matrice (ou plus), et bien comme c'est un tableau de tableaux (de tableaux ...), ça renvoie le nombre d'éléments. Après rien ne nous interdit de faire Array.length table.(i) !
let nouveau = Array.sub t i n
Crée un pointeur vers le sous tableau de t commençant à l'indice i et de taille n. Ça a l'air utile comme ça, mais dans la pratique on s'en sert presque jamais. D'autant que changer 'nouveau' modifierait t...

Chaines de caractères

Une chaîne est un tableau de caractères. A ce titre, presque tout est pareil que pour les tableaux, à quelques exceptions :

Listes

Elles sont prédominantes en CamL, qui utilise beaucoup les fonctions récursives, et donc les listes dans les arguments. Attention, les éléments des listes ne sont pas modifiables !!! (sauf les listes de tableaux, de pointeurs sur des entiers, et de chaines de caractères (qui sont des tableaux), car les tableaux sont des pointeurs, donc on ne modifie pas le pointeur lui-même (il reste constant) mais là vers où il pointe).

L'habitude est donc en CamL de refaire toute une liste quand on veut juste changer un élément, ... Ça a l'air énervant comme ça, mais il n'y a pas souvent de choses à changer en pratique, et d'autre part s'il faut le faire, utilisez des pointeurs.

Déclaration d'une liste
let listevide = [] in... ou let listepleine = ["toto"; "tata"; "titi"] in...
Adjonction d'un élément au début de la liste
3 :: l
Concaténation
Soit vous vous la reprogrammez, soit vous faites appel à la bibliothèque List.( #open "list";; tout en haut de votre code)

Il faut savoir qu'une liste c'est : un élément, et un pointeur sur une autre liste. En clair l'insertion d'un élément à la fin d'une liste... c'est plus compliqué que ça en a l'air

Exemple de l'utilisation de 'match' pour afficher tous les éléments d'une liste d'entiers :
let affiche = function
| [] -> ()
| t :: q -> (print_int t; affiche q);;

Vous voyez, c'est du code rapide et joli !!!

Fonction qui applique une fonction a tous les éléments d'une liste
let maper f = function
| [] -> []
| t :: q -> (f t) :: (maper f q);;
f est l'argument de notre fonction 'maper', (et le deuxième argument est une liste, mais ça se voit pas parce qu'on a écrit function pour éviter d'avoir à écrire match liste with...

Types utilisateurs

C'est pour ainsi dire le domaine de prédilection de CamL.

type montype = Ceci | Cela | UnEntier of int | Autre of montype;;
Déclare un type nouveau : un élément sera soit un Ceci, soit un Cela, soit un entier, soit quelque chose de notre type. En soi cette construction ci ne sert à rien, je vous l'accorde, mais c'est pour donner une idée. Par exemple, si vous avez la carte d'un jeu 2D, vous pouvez faire type laby = Mur | Arbre of string | Rien | Mechant of int (string dira si c'est un peuplier, ... et Mechant le nombre de points de vie/d'expérience de celui-ci)
type maliste = Vide | Couple of int * maliste;;

Le type liste étant peut-être déjà implémenté en CamL, mais rien ne vous empêche d'essayer de faire le votre.

C'est pour les types utilisateurs que le match a été construit ! Ainsi, on écrira :

match liste with
| Vide -> ...
| Couple (b, c) -> ....;;
type arbre = Feuille of int | Noeud of arbre * arbre;;
Définition d'un arbre. Remarquez la puissance de ce mécanisme de définition, bien moins encombrant que le C++ ou Java !!!
type 'a liste = Vide | Triple of 'a * 'b * ('a liste);;
Ceci vous montre comment définir quelque chose un peu comme les "template" de C++ : au lieu de définir liste comme Vide | Couple of int * liste puis pareil mais pour les listes de chaînes de caractère, puis ..., (alors que c'est la même structure au final), et bien là c'est ça.
type personne = { prenom : string; mutable age : int };;
Enfin, on a la possibilité de créer des enregistrements. Je vous donne la syntaxe, mais pour être franc je ne m'en sers jamais. Et d'ailleurs les livres ou tutoriels donnent toujours le même exemple pour dire à quel point il y a de multiples possibilités d'utilisation, et comment c'est beau. Quel comble. Je vous donne donc le même exemple. A savoir que si on met le mot clé mutable, on pourra changer la valeur de la propriété, sinon non. Syntaxe de lecture/écriture : let thomas = {prenom = "thomas", age = "20"} in thomas.age <- 30;;

Commandes en vrac

Vous avez pu le constater, CamL n'est pas fait pour utiliser Excel, gérer vos fichiers sur l'ordinateur, ou autre, mais vraiment faire de la programmation pure.

float_of_int, int_of_float, string_of_int, ...
Conversion d'un type à un autre
print_int, print_newline(), print_string, print_float, ...
Les analogues sont avec Printf.print : "%d", %s et %f respectivement. Exemple : "%s a %d ans et %f part d'année" "Thomas" 21 0.34
Intéraction avec l'utilisateur :
let lire_entier() = (fun i -> Scanf.scanf " %d" (fun x -> x)) ; Voir Array.init pour lire un tableau. Remplacer %d par %s pour lire une chaîne de caractères.
a lsl i
Si le i_eme octet de a est à 0 ou 1. Très utile pour faire les conversions en base binaire !
failwith "erreur : division par 0"

Ça peut servir à 2 choses : quand on a envie de dire à une fonction qu'il faut s'arrêter là et ne pas continuer, parce qu'on est dans un cas où l'on n'est pas sensé être, mais que CamL pour compiler a besoin que notre fonction retourne la même chose que dans les autres cas. Plutôt que de mettre quelque chose de bidon, on met un petit failwith, et hop on est tranquille. Après, c'est aussi une manière de lancer une exception en CamL. On la rattrape avec try (ici le code où il y aura des exceptions à rattraper) with (et ici le compilateur va matcher ce que l'on recoit.

Exemple :

let f n =
  if n = 1 failwith "trop petit";
  if n = 2 failwith 2;
  5;;

try
  print_int (f 4)
with
| 2 -> ...
| "trop petit" -> ...
| _ -> (* une autre erreur, mais une erreur quand même, donc pas le cas où f renvoie 5 *)

Autres sites traitant de CamL

A votre grand damne, il n'y en a pas vraiment beaucoup. Mais comme à tout malheur quelque chose est bon, CamL n'a pas non plus des dizaines d'API cachées, il n'y a que très peu de commandes différentes et à partir de là, les seuls problèmes que vous aurez seront de nature algorithmique. Je vous conseille de jeter un oeil à ma page de codes sources, car CamL est un langage de type fonctionnel et il faut s'y habituer. Cela ne veut pas dire que je suis un exemple à suivre, mais bon... regarder mon code peut vous donner une idée du type de programme à faire.

Je m'explique : si vous devez écrire la fonction factorielle(n), on peut faire :

let rec facto n =
  if n = 1 then 
    1
  else
    n * (facto (n - 1));;
ou bien
let facto n =
  let p = ref 1 in
  for k = 1 to n
    p := !p * k
  done;
  !p;;

La "bonne" manière de programmer est la première : le code est plus clair, et correspond plus à la manière dont la fonction est définie en vraie, bref plus intuitif. Pourtant, il y a n appels de fonctions, ce qui ralentit le programme. L'esprit CamL consiste à faire fi de la mémoire et du temps de programmation (pas complètement non plus, mais bon). En clair, vu la puissance des ordinateurs de nos jours, on n'est pas à 3 octets près. Donc autant écrire un code propre et net, et qui sera rapide à déboguer, qu'un espèce de gros machin destiné à faire économiser 1 CPU.

Fragments de code

fonction d'association

Elle cherche dans une liste de couples (clé,valeur) une clé et renvoie le cas échéant la valeur associée. On peut renvoyer -1, ou "" (ça dépend du type de valeur) si l'on ne trouve pas le résultat, ou un failwith, mais c'est énervant à écrire

let rec assoc x = function
| [] -> failwith "non"
| (a, b) :: q -> if a = x then b else assoc x q;;
Arbres n-aires et opérations basiques

Le type arbre n-aire est d'abord défini : soit c'est une feuille, soit c'est un noeud où il y a p branches (p pouvant varier entre les différents noeuds), et donc il se caractérise par une liste d'arbres n-aires. Puis l'on définit les grandeurs usuelles sur les arbres : nombres de feuilles, de noeuds, et profondeur d'un tel arbre.

type ('n, 'f) arbre_naire = Feuille of 'f | Noeud of 'n * ('n, 'f) arbre_naire list;;

let rec it_list compteur f = function
| [] -> compteur (* on renvoit le compteur une fois la liste parcourue en entier *)
| arbre :: resteliste -> it_list (compteur + f arbre) f resteliste;;

let rec nb_feuilles = function
| Feuille _ -> 1
| Noeud (_, l) -> it_list 0 nb_feuilles l);;

let rec nb_noeuds = function
| Feuille _ -> 0
| Noeud (_, l) -> it_list 1 nb_noeuds l);;

Pour comprendre un petit peu ce code, par exemple celui qui compte le nombre de feuilles : un arbre est soit une feuille donc il a 1 feuille, soit c'est une liste d'arbre, et c'est la somme du nombre de feuilles des arbres de cette liste. On a donc envie de faire n = 0 puis pour chaque arbre de cette liste, n = n + le nombre de feuilles de l'arbre. Mais c'est moche de l'écrire comme ça. On itère donc avec it_list sur chaque noeud de la liste. Le 'a' de l'argument de it_list joue donc le rôle de compteur, comme si on avait une variable globale que l'on incrémentait, sauf que là, elle se transmet, et f est la fonction de ce qu'on va compter. Ainsi pour le nombre de feuilles, on appelle it_list sur notre liste et 0 parce qu'on n'a encore rien compté. it_list va appliquer f, c'est à dire nb_feuilles à chaque arbre de la liste, et va donc ajouter tout ça.

idem avec la profondeur

let rec it_list2 a f = function
| [] -> a
| t :: q -> it_list2 (f a t) f q;;

let rec profondeur = function
| Feuille _ -> 1
| Noeud (_, l) -> (let f a b = max a (profondeur b) in
                   1 + it_list2 0 f l);;

Remarque : la fonction est ici plus complexe : a prend toujours le rôle de compteur, mais ici la fonction f prend 2 arguments. En effet, on aurait pu écrire it_list3 de même, avec it_list3 (max a (f t)) f q seulement, pour dire que la hauteur d'un arbre est le max des hauteurs de chaque sous-arbre, mais en faisant ici une fonction it_list2 plus complexe, on couvre plus de cas : it_list dit qu'il faut ajouter l'argument 'a', notre compteur, à tous les autres, et it_list3 dit qu'il faut en prendre le max. Et bien autant faire it_list2 qui nous laisse le choix de fournir f.

Pour nb_feuilles, la fonction f passée en argument ne serait pas nb_feuilles, mais let f a b = (a + nb_feuilles b) in it_list2 0 f l;; et idem pour nb_noeuds : let f a b = (a + nb_noeuds b) in it_list2 1 f l;;

Laissez un commentaire !

Pas besoin de vous connecter, commencez à taper votre nom et une case "invité" apparaîtra.

Tutoriel VB6/VBA
Bases pour créer une belle app en VB6/VBA + astuces et codes sources
Programmation
Tutoriels et conseils pour bien démarrer, outils à utiliser
Tutoriel Java
Bases pour créer une appli en Java avec interface + astuces et code sources
Tutoriel AppleScript
Les bases pour créer une belle app en AppleScript + astuces et codes sources
iOS Code Signing Provisioning Profile
In French and English
Bruce Willis, on a besoin de toi !
Pétition écologique : l'affaire du siècle
iPhone App
Le succès frappe à ma porte mais se décide à entrer...
Accusé de réception / Email read notification
Activer l'accusé de réception dans Mail.
Activate the email read notification in Mail.
Création du blog avant le Japon
Et Dieu créa la Terre.
4 jours en Alaska
Je m'en irai dormir dans le paradis blanc
Automator et Applescript
Impimer plusieurs liens URL / pages internet en PDF
Print many URL / webpages in PDF
Las Vegas
'What happens in Vegas stays in Vegas'
L'avenir du thon rouge
Vote aujourd'hui à Doha...
Foxconn
la surprise dévoilée
Soirée au café-théâtre
Le Matana (Paris 5ème), un café à découvrir ?
Driving license
Un pas de plus vers la citoyenneté américaine !
Kyoto - rangs d'oignon et lac biwa
ils sont fous ces japonais !
Mon stage au Japon se termine
Youhouhou !
Honteux !
réaction à chaud sur le match France-Irlande
Fabrication des iPhone Apps
Vue de derrière les coulisses
Manifs
honteux. Comme d'habitude
Kyoto - Emploi du temps
les jours passent et se ressemblent