Réf:
J'ai cherché beaucoup de liens, mais il semble que personne ne puisse l'expliquer spécifiquement. Quelqu'un pourrait-il donner du code (utiliser javaScript) pour l'expliquer?
Pour autant que je comprenne le sujet, les effets algébriques sont actuellement un concept académique/expérimental qui vous permet de modifier certains éléments de calcul (comme les appels de fonction, les instructions d'impression, etc.) appelés "effets" en utilisant un mécanisme qui ressemble à throw catch
L'exemple le plus simple auquel je peux penser dans un langage comme JavaScript est la modification du message de sortie dans disons console.log
. Supposons que vous souhaitiez ajouter "Debug Message:" devant tous vos console.log
déclarations pour une raison quelconque. Ce serait un problème en JavaScript. Fondamentalement, vous devez appeler une fonction sur chaque console.log
ainsi:
function logTransform(msg) { return "Debug Message: " + msg; }
console.log(logTransform("Hello world"));
Maintenant, si vous avez beaucoup de console.log
instructions, chacune d'elles doit être modifiée si vous souhaitez introduire la modification dans la journalisation. Maintenant, le concept d'effets algébriques vous permettrait de gérer l '"effet" de console.log
sur le système. Pensez-y comme console.log
levant une exception avant l'invocation et cette exception (l'effet) bouillonne et peut être gérée. La seule différence est: si elle n'est pas gérée, l'exécution se poursuivra comme si de rien n'était. Non, ce que cela vous permet de manipuler le comportement de console.log
dans une portée arbitraire (globale ou juste locale) sans manipuler l'appel réel à console.log
. Pourrait ressembler à ceci:
try
{
console.log("Hello world");
}
catch effect console.log(continuation, msg)
{
msg = "Debug message: " + msg;
continuation();
}
Notez que ce n'est pas du JavaScript, je fais juste la syntaxe. Comme les effets algébriques sont une construction expérimentale, ils ne sont pris en charge nativement dans aucun des principaux langages de programmation que je connais (il existe cependant plusieurs langages expérimentaux comme eff https://www.eff-lang.org/learn/ ) . J'espère que vous comprendrez approximativement comment mon code composé doit fonctionner. Dans le bloc try catch, l'effet qui pourrait être généré par console.log
peut être manipulé. La continuation est une construction semblable à un jeton qui est nécessaire pour contrôler quand le flux de travail normal doit continuer. Ce n'est pas nécessaire d'avoir une telle chose mais cela vous permettrait de faire des manipulations avant et après console.log
(par exemple, vous pouvez ajouter un message de journal supplémentaire après chaque console.log)
Dans l'ensemble, les effets algébriques sont un concept intéressant qui aide à résoudre de nombreux problèmes réels dans le codage, mais il peut également introduire certains pièges si les méthodes se comportent soudainement différemment que prévu. Si vous souhaitez utiliser des effets algébriques dès maintenant en JavaScript, vous devrez écrire un cadre pour lui-même et vous ne pourrez probablement pas appliquer des effets algébriques à des fonctions essentielles telles que console.log
en tous cas. Fondamentalement, tout ce que vous pouvez faire maintenant est d'explorer le concept à une échelle abstraite et d'y réfléchir ou d'apprendre l'une des langues expérimentales. Je pense que c'est aussi la raison pour laquelle de nombreux articles introductifs sont si abstraits.
Il est difficile d'acquérir une solide compréhension théorique des effets algébriques sans une base dans la théorie des catégories, je vais donc essayer d'expliquer son utilisation en termes simples, sacrifiant peut-être une certaine précision.
Un effet de calcul est tout calcul qui inclut une altération de son environnement. Par exemple, des choses comme la capacité totale du disque, la connectivité réseau sont des effets externes qui jouent un rôle dans des opérations telles que la lecture/écriture de fichiers ou l'accès à une base de données. Tout ce qu'une fonction produit, en plus de la valeur qu'elle calcule, est un effet de calcul. Du point de vue de cette fonction, même une autre fonction qui accède à la même mémoire que cette fonction peut être considérée comme un effet.
Voilà la définition théorique. En pratique, il est utile de penser à un effet comme toute interaction entre ne sous-expression et n contrôle central qui gère les ressources globales dans un programme. Parfois, une expression locale peut avoir besoin d'envoyer des messages au contrôle central pendant l'exécution, ainsi que suffisamment d'informations pour qu'une fois le contrôle central effectué, il puisse reprendre l'exécution suspendue.
Pourquoi faisons-nous cela? Parce que parfois les grandes bibliothèques ont de très longues chaînes d'abstractions, ce qui peut devenir compliqué. L'utilisation des "effets algébriques" nous donne une sorte de raccourci pour passer des choses entre des abstractions, sans passer par toute la chaîne.
Comme exemple JavaScript pratique, prenons une bibliothèque d'interface utilisateur comme ReactJS. L'idée est que l'interface utilisateur peut être écrite comme une simple projection de données.
Ce serait par exemple la représentation d'un bouton.
function Button(name) {
return { buttonLabel: name, textColor: 'black' };
}
'John Smith' -> { buttonLabel: 'John Smith', textColor: 'black' }
En utilisant ce format, nous pouvons créer une longue chaîne d'abstractions composables. Ainsi
function Button(name) {
return { buttonLabel: name, textColor: 'black' };
}
function UsernameButton(user) {
return {
backgroundColor: 'blue',
childContent: [
Button(user.name)
]
}
}
function UserList(users){
return users.map(eachUser => {
button: UsernameButton(eachUser.name),
listStyle: 'ordered'
})
}
function App(appUsers) {
return {
pageTheme: redTheme,
userList: UserList(appUsers)
}
}
Cet exemple a quatre couches d'abstraction composées ensemble.
App -> UserList -> UsernameButton -> Button
Supposons maintenant que pour chacun de ces boutons, je dois hériter du thème de couleur de la machine sur laquelle il fonctionne. Disons que les téléphones portables ont du texte rouge, tandis que les ordinateurs portables ont du texte bleu.
Les données du thème sont dans la première abstraction (App). Il doit être implémenté dans la dernière abstraction (Button).
La façon ennuyeuse serait de transmettre les données du thème, de l'application au bouton, en modifiant chaque abstraction en cours de route.
L'application transmet les données de thème à UserList UserList les transmet à UserButton UserButton les transmet à Button
Il devient évident que dans les grandes bibliothèques avec des centaines de couches d'abstraction, c'est une énorme douleur.
Une solution possible est de transmettre l'effet, via un gestionnaire d'effets spécifique et de le laisser continuer quand il le faut.
function PageThemeRequest() {
return THEME_EFFECT;
}
function App(appUsers) {
const themeHandler = raise new PageThemeRequest(continuation);
return {
pageTheme: themeHandler,
userList: UserList(appUsers)
}
}
// ...Abstractions in between...
function Button(name) {
try {
return { buttonLabel: name, textColor: 'black' };
} catch PageThemeRequest -> [, continuation] {
continuation();
}
}
Ce type de gestion des effets, où une abstraction dans une chaîne peut suspendre ce qu'elle fait (implémentation de thème), envoyer les données nécessaires au contrôle central (App, qui a accès à des thèmes externes), et transmet les données nécessaires à la poursuite, est un exemple extrêmement simpliste de gestion des effets algébriquement.
TL; DR: En bref, les effets algébriques sont un mécanisme d'exception qui permet à la fonction throw
ing de continuer son fonctionnement.
Essayez de penser aux effets algébriques comme une sorte de mécanisme try
/catch
, où le gestionnaire catch
ne se contente pas de "gérer l'exception", mais peut fournit une entrée à la fonction qui a levé l'exception. L'entrée du gestionnaire catch
est ensuite utilisée dans la fonction de lancement, qui continue comme s'il n'y avait pas d'exception.
Prenons une fonction qui a besoin de données pour exécuter sa logique:
function throwingFunction() {
// we need some data, let's check if the data is here
if (data == null) {
data = throw "we need the data"
}
// do something with the data
}
Ensuite, nous avons le code qui appelle cette fonction:
function handlingFunction() {
try {
throwingFunction();
} catch ("we need the data") {
provide getData();
}
}
Comme vous le voyez, l'instruction throw
est une expression évaluant les données fournies par le gestionnaire catch
(j'ai utilisé ici le mot-clé provide
, qui afaik n'existe dans aucune programmation langue d'aujourd'hui).
Les effets algébriques sont un concept très général et basique. Cela peut être vu par le fait que de nombreux concepts existants peuvent être exprimés en effets algébriques.
try
/catch
Si nous avions des effets algébriques mais pas d'exceptions dans notre langage de programmation préféré, nous pourrions simplement omettre le mot clé provide
dans le gestionnaire catch
, et voilà, nous aurions un mécanisme d'exception.
En d'autres termes, nous n'aurions pas besoin d'exceptions si nous avions des effets algébriques.
async
/await
Regardez à nouveau le pseudo-code ci-dessus. Supposons que les données dont nous avons besoin doivent être chargées sur le réseau. Si les données ne sont pas encore là, nous renvoyons normalement une promesse et utilisons async
/await
pour les gérer. Cela signifie que notre fonction devient une fonction asynchrone, qui ne peut être appelée qu'à partir de fonctions asynchrones. Cependant, les effets algébriques sont également capables de ce comportement:
function handlingFunction() {
try {
throwingFunction();
} catch ("we need the data") {
fetch('data.source')
.then(data => provide data);
}
}
Qui a dit que le mot clé provide
devait être utilisé immédiatement?
En d'autres termes, si nous avions eu des effets algébriques avant async
/await
, il ne serait pas nécessaire d'encombrer les langues avec eux. De plus, les effets algébriques ne rendraient pas nos fonctions coloré - notre fonction ne devient pas aynchrone du point de vue du langage.
Disons que nous voulons avoir des instructions de journal dans notre code, mais nous ne savons pas encore de quelle bibliothèque de journalisation il s'agit. Nous voulons juste quelques instructions de journal générales (j'ai remplacé le mot clé throw
par le mot clé effect
ici, pour le rendre un peu plus lisible - notez que effect
n'est pas un mot clé dans n'importe quelle langue que je connais):
function myFunctionDeepDownTheCallstack() {
effect "info" "myFunctionDeepDownTheCallstack exits"
// do some stuff
if (warningCondition) {
effect "warn" "myFunctionDeepDownTheCallstack has a warningCondition"
}
// do some more stuff
effect "info" "myFunctionDeepDownTheCallstack begins"
}
Et puis nous pouvons connecter n'importe quel cadre de log en quelques lignes:
try {
doAllTheStuff();
}
catch ("info" with message) {
log.Info(message);
}
catch ("warn" with message) {
log.Warn(message);
}
De cette façon, l'instruction log et le code qui effectue réellement la journalisation sont séparés.
Comme vous pouvez le voir, le mot clé throw
n'est pas vraiment adapté dans le contexte des effets algébriques très généraux. Les mots clés les plus appropriés seraient effect
(tel qu'utilisé ici) ou perform
.
Il existe d'autres constructions de langage ou de bibliothèque existantes qui pourraient être facilement réalisées à l'aide des effets algébriques:
yield
. Un langage avec des effets algébriques n'a pas besoin de l'instruction yield
.Vous pouvez vérifier effets algébriques . Il s'agit d'une bibliothèque qui implémente de nombreux concepts d'effets algébriques en javascript à l'aide de fonctions de générateur, y compris de multiples continuations. Il est beaucoup plus facile de comprendre les effets algébriques en termes de fonctions try-catch (effet d'exception) et de générateur.