Marcelo Cantos a donné une assez bonne explication mais je pense qu'il est possible de le rendre légèrement plus précis.
Un type de chose est composable lorsque plusieurs instances peuvent être combinées d'une certaine manière pour produire le même type de chose.
Composabilité de la structure de contrôle. Les langages comme le C font une distinction entre expressions qui peuvent être composées à l'aide d'opérateurs pour produire de nouvelles expressions, et déclarations qui peuvent être composées à l'aide de structures de contrôle telles que if
, for
et la "structure de contrôle de séquence" qui exécute simplement les déclarations dans l'ordre. Le problème de cet arrangement est que ces deux catégories ne sont pas sur un pied d'égalité -- de nombreuses structures de contrôle utilisent des expressions (par exemple, l'expression évaluée par if
pour choisir la branche à exécuter), mais les expressions ne peuvent pas utiliser de structures de contrôle (par exemple, vous ne pouvez pas renvoyer un fichier for
boucle). Bien qu'il puisse sembler fou ou dénué de sens de vouloir "retourner une for
En fait, l'idée générale de traiter les structures de contrôle comme des objets de première classe qui peuvent être stockés et transmis n'est pas seulement possible mais utile. Dans les langages fonctionnels paresseux comme Haskell, les structures de contrôle telles que if
y for
peuvent être représentées comme des fonctions ordinaires, qui peuvent être manipulées dans des expressions comme n'importe quel autre terme, permettant des choses telles que des fonctions qui "construisent" d'autres fonctions en fonction des paramètres qui leur sont passés, et les retournent à l'appelant. Ainsi, alors que le langage C (par exemple) divise "les choses qu'un programmeur pourrait vouloir faire" en deux catégories et limite la manière dont les objets de ces catégories peuvent être combinés, Haskell (par exemple) n'a qu'une seule catégorie et n'impose pas de telles limites, donc dans ce sens, il offre plus de composabilité.
Composabilité du fil. Je vais supposer, comme Marcelo Cantos, que vous parlez réellement de la composabilité des threads qui utilisent des locks/mutex. Il s'agit d'un cas un peu plus délicat, car à première vue nous peut avoir des threads qui utilisent des verrous multiples ; mais le point important est que nous ne pouvons pas avoir des threads qui utilisent des verrous multiples avec les garanties qu'ils sont censés avoir .
Nous pouvons définir un verrou comme un type de chose sur lequel certaines opérations peuvent être effectuées, avec certaines garanties. Une garantie est la suivante : supposons qu'il y ait un objet de verrouillage x
à condition que chaque processus qui appelle lock(x)
appelle éventuellement unlock(x)
tout appel à lock(x)
reviendra finalement avec succès avec x
verrouillé par le thread/processus actuel. Cette garantie simplifie grandement le raisonnement sur le comportement du programme.
Malheureusement, s'il y a plus d'une serrure dans le monde, ce n'est plus vrai. Si le thread A appelle lock(x); lock(y);
et le fil B appelle lock(y); lock(x);
alors il est possible que A saisisse la serrure x
et B saisit la serrure y
et ils attendront tous deux indéfiniment que l'autre thread libère l'autre verrou : deadlock. Ainsi, les verrous ne sont pas composables, car lorsque vous en utilisez plus d'un, vous ne pouvez pas simplement prétendre que cette importante garantie est toujours valable non sans avoir analysé le code en détail pour voir comment il gère les verrous . En d'autres termes, vous ne pouvez plus vous permettre de traiter les fonctions comme des "boîtes noires".
Les choses qui sont composables sont bonnes car elles permettent abstractions Ils nous permettent de raisonner sur le code sans avoir à nous soucier de tous les détails, ce qui réduit la charge cognitive du programmeur.