©hijiangtao
function getName(user) {
let name = user.name;
if (name === null) {
throw new Error('A girl has no name');
}
return name;
}
function makeFriends(user1, user2) {
user1.friendNames.add(getName(user2));
user2.friendNames.add(getName(user1));
}
const arya = { name: null };
const gendry = { name: 'Gendry' };
try {
makeFriends(arya, gendry);
} catch (err) {
console.log("Oops, that didn't work out: ", err);
}
©hijiangtao
©hijiangtao
©hijiangtao
function getName(user) {
let name = user.name;
if (name === null) {
name = perform 'ask_name';
}
return name;
}
function makeFriends(user1, user2) {
user1.friendNames.add(getName(user2));
user2.friendNames.add(getName(user1));
}
const arya = { name: null };
const gendry = { name: 'Gendry' };
try {
makeFriends(arya, gendry);
} handle (effect) {
if (effect === 'ask_name') {
resume with 'Arya Stark';
}
}
©hijiangtao
function getName(user) {
let name = user.name;
if (name === null) {
name = perform 'ask_name';
}
return name;
}
try {
makeFriends(arya, gendry);
} handle (effect) {
if (effect === 'ask_name') {
resume with 'Arya Stark';
}
}
1⃣️ Perform an effect
2⃣️ Jump to the handler
3⃣️ Resume with a value
4⃣️ End up back here with "name"
©hijiangtao
©hijiangtao
// If we want to make this async...
async getName(user) {
// ...
}
// Then this has to be async too...
async function makeFriends(user1, user2) {
user1.friendNames.add(await getName(user2));
user2.friendNames.add(await getName(user1));
}
// And so on...
©hijiangtao
try {
makeFriends(arya, gendry);
} handle (effect) {
if (effect === 'ask_name') {
setTimeout(() => {
resume with 'Arya Stark';
}, 1000);
}
}
try {
makeFriends(arya, gendry);
} handle (effect) {
if (effect === 'ask_name') {
resume with 'Arya Stark';
}
}
Mechanics of algebraic effects
When we throw an error, the JavaScript engine “unwinds the stack”, destroying local variables in the process. However, when we perform an effect, our hypothetical engine would create a callback with the rest of our function, and resume with calls it.
©hijiangtao
©hijiangtao
function enumerateFiles(dir) {
const contents = perform OpenDirectory(dir);
perform Log('Enumerating files in ', dir);
for (let file of contents.files) {
perform HandleFile(file);
}
perform Log('Enumerating subdirectories in ', dir);
for (let directory of contents.dir) {
// We can use recursion
// or call other functions with effects
enumerateFiles(directory);
}
perform Log('Done');
}
©hijiangtao
let files = [];
try {
enumerateFiles('C:\\');
} handle (effect) {
if (effect instanceof Log) {
myLoggingLibrary.log(effect.message);
resume;
} else if (effect instanceof OpenDirectory) {
myFileSystemImpl.openDir(effect.dirName, (contents) => {
resume with contents;
});
} else if (effect instanceof HandleFile) {
files.push(effect.fileName);
resume;
}
}
// The `files` array now has all the files
©hijiangtao
©hijiangtao
// ES2100
perform Timeout(1000);
perform Fetch('google.com');
perform ReadFile('file.txt');
perform Timeout(1000);
perform Fetch('google.com');
perform ReadFile('file.txt');
function LikeButton() {
// How does useState know
// which component it's in?
const [isLiked, setIsLiked]
= useState(false);
}
©hijiangtao
©hijiangtao
©hijiangtao
https://overreacted.io/algebraic-effects-for-the-rest-of-us/
https://github.com/ocamllabs/ocaml-effects-tutorial
https://www.janestreet.com/tech-talks/effective-programming/
https://www.youtube.com/watch?v=hrBq8R_kxI0
https://en.wikibooks.org/wiki/Common_Lisp/Advanced_topics/Condition_System
https://jlongster.com/Whats-in-a-Continuation
©hijiangtao