Premier billet d'une série au fil de ma lecture de [6].
Dans le chapitre premier, Simondon écrit:
Il s'intéresse donc à la génèse des objets techniques, ce qui l'amène
à décrire un processus de concrétisation, faisant passer un objet
abstrait à un objet concret, ou plutôt d'un objet plus abstrait (ou moins
concret) à un objet moins abstrait (ou plus concret).
Simondon décrit l'objet technique abstrait comme composé d'éléments
possédant chacun une fonction unique et intéragissant peu ou pas
avec les autres:
Au contraire, l'objet technique concret est tel un
moteur actuel où "chaque pièce importante est tellement rattachée aux autres
par des échanges réciproques d'énergie qu'elle ne peut pas être autre
qu'elle n'est." ([6] page 23)
De plus, l'objet abstrait présente des défauts qui seront corrigés
dans les évolutions ultérieures de l'objet, dûs au fait qu'il
n'y a pas de synergie entre les éléments assurant les différentes fonctions.
Enfin, le moteur concret d'aujourd'hui est le moteur abstrait de demain,
puisque le moteur évolue par un processus de concrétisation.
Prenons maintenant un exemple pour voir comment ces idées s'appliquent
aux objets techniques que sont les logiciels.
Imaginons que nous souhaitons définir une fonction prenant en
entrée une liste de listes d'entiers, et affichant ces listes
d'entiers ainsi que leur moyenne respective, triées par ordre croissant.
L'algorithme est simple: trier la liste des listes en entrée selon
leur moyenne, puis afficher la liste triée.
En OCaml1,
nous pourrons ainsi définir
une fonction retournant la moyenne d'une liste d'entiers:

# let average = function [] -> assert false (* ne traitons pas les listes vides pour l'exemple *) | l -> let sum = List.fold_left (+) 0 l in (float sum) /. (float (List.length l)) ;; val average : int list -> float = <fun>
Puis nous définissons une fonction de tri de deux listes, d'après leur moyenne
respective:

# let sort_list = let compare_list l1 l2 = compare (average l1) (average l2) in fun list -> List.sort compare_list list ;; val sort_list : int list list -> int list list = <fun>
Nous définissons egalement la fonction d'affichage d'une liste et sa moyenne:

# let print_list = let print_one list = let avg = average list in let s_list = List.map string_of_int list in let s = Printf.sprintf "[%s] => %f" (String.concat " ; " s_list) avg in print_endline s in fun list -> List.iter print_one list ;; val print_list : int list list -> unit = <fun>
Enfin, la fonction sort_and_print sera la fonction représentant
notre objet technique. Elle prent une liste de listes d'entiers et affiche
les listes et leurs moyennes triées par ordre croissant selon leurs moyennes.
La fonction se borne à composer la fonction de tri et la fonction d'affichage:

# let sort_and_print list = print_list (sort_list list);; val sort_and_print : int list list -> unit = <fun>

# let data = [ [ 1 ; 2 ; 3 ; 4 ] ; [ 1 ; 3 ; 4 ; 2 ] ; [ 10 ; -2 ; 5 ; 12 ] ; [ -1 ; -2 ; 5 ] ; ];; val data : int list list = [[1; 2; 3; 4]; [1; 3; 4; 2]; [10; -2; 5; 12]; [-1; -2; 5]] # sort_and_print data;; [-1 ; -2 ; 5] => 0.666667 [1 ; 2 ; 3 ; 4] => 2.500000 [1 ; 3 ; 4 ; 2] => 2.500000 [10 ; -2 ; 5 ; 12] => 6.250000 - : unit = ()
La fonction sort_and_print, bien que correcte, présente un défaut:
Elle calcule plus d'une fois la moyenne de chaque liste. En effet, les deux fonctions
qui la composent (sort_list et print_list) appellent
chacune la fonction average. Le tri de la liste effectue même le caclul
de la moyenne à chaque comparaison de liste.
sort_and_print est donc un bon exemple d'objet technique abstrait,
selon la définition de Simondon: Il est composé de deux "éléments définis par
leur fonction complète et unique". sort_list et print_list
prennent en effet une liste d'entiers et font tous les calculs nécessaires pour
remplir leur fonction: trier et afficher.
Dans son ouvrage, Simondon s'appuie sur des exemples mécaniques et électriques,
et les problèmes posés par les objets techniques abstraits sont par exemples
l'échauffement, la pression, la précision d'un faisseau d'électrons.
En ce qui concerne les logiciels, les problèmes posés par des fonctions abstraites
au sens de Simondon seront plutôt à chercher dans un surplus de temps ou d'occupation
mémoire pour effectuer les opérations.
C'est le cas pour notre objet technique: la fonction sort_and_print
prend bien plus de temps que nécessaire à cause des appels répétés à la fonction
de calcul de la moyenne.
Pour l'améliorer, nous pouvons effectuer le calcul des moyennes une fois pour toutes,
puis utiliser le résultat à la fois dans la fonction de tri et dans la fonction
d'affichage. La fonction average reste inchangée mais les fonctions
de tri et d'affichage doivent être modifiées pour utiliser les moyennes préalablement
calculées. Ainsi, ces deux fonctions ne prendront plus seulement une liste de listes
d'entiers (de type int list list) mais une liste de listes de paires
(moyenne, liste d'entiers) (donc de type (float * int list) list).

# let sort_list = let compare_list (avg1, l1) (avg2, l2) = compare avg1 avg2 in fun list -> List.sort compare_list list ;; val sort_list : ('a * 'b) list -> ('a * 'b) list = <fun> # let print_list = let print_one (avg, list) = let s_list = List.map string_of_int list in let s = Printf.sprintf "[%s] => %f" (String.concat " ; " s_list) avg in print_endline s in fun list -> List.iter print_one list ;; val print_list : (float * int list) list -> unit = <fun>
Notre fonction sort_and_print voit alors sa structure légèrement
modifiée suite au réagencement des fonctions:

# let sort_and_print list = (* calcul de la moyenne de chaque liste, en gardant la liste originale *) let list_with_avg = List.map (fun l -> (average l, l)) list in (* trie et affichage en utilisant la nouvelle structure *) print_list (sort_list list_with_avg) ;; val sort_and_print : int list list -> unit = <fun>
Vérifions que le résultat reste inchangé:

# sort_and_print data;; [-1 ; -2 ; 5] => 0.666667 [1 ; 2 ; 3 ; 4] => 2.500000 [1 ; 3 ; 4 ; 2] => 2.500000 [10 ; -2 ; 5 ; 12] => 6.250000 - : unit = ()
La transformation que nous avons faite subir à notre fonction sort_and_print
relève de ce que Simondon appelle le processus de concrétisation: Il s'agit de supprimer
les défauts de la première version, non pas en cherchant uniquement à séparer la fonction
de calcul des fonctions de tri et d'affichage, mais également en augmentant la synergie
entre les fonctions.
En effet, les fonctions de tri et d'affichage se contentent maintenant de trier et d'afficher,
les calculs des moyennes étant faits en amont. Les fonctions communiquent par une structure
de donnée enrichie (contenant la liste et la moyenne), ce qui permet aux fonctions de fonctionner
en synergie.
Cette façon de procéder est décrite par Simondon:
Evidemment, l'exemple de code montré ici est simple, mais l'approche reste valable
à large échelle et pour des logiciels plus compliqués. Ce processus de concrétisation
est souvent appelé "optimisation" en développement logiciel.
Par ailleurs, même si le développeur expérimenté verra d'emblée comment avoir
un code plus concret comme dans l'exemple ci-dessus, il raisonnera tout de même en deux
étapes: d'abord penser un objet abstrait, puis un objet concret qu'il implémente.
Lorsque le problème est plus ardu, l'historique du code reflètera les évolutions:
des fonctions abstraites (au sens de Simondon), concrétisées au fur et à mesure que
le problème est mieux compris et que l'objet technique est spécialisé.
Le processus de concrétisation décrit par Simondon s'applique bien
au processus de développement d'un logiciel, comme nous venons de le voir.
Reste à savoir jusqu'où il faut suivre ce processus.
Dans un prochain billet, nous verrons notamment les deux types de perfectionnements,
continu et discontinu. A suivre, donc...