原文: Functional Computational Thinking — What is a monad?
原文作者是在学习了 Haskell 之后,又看到了 Why Do Manods Matter 的相关视频,根据视频中Haskell 函数组合的例子,转用javascript实现(nodejs环境)。
首先 ,我们都知道 函数组合:
const add1 = x => x + 1;
const mul3 = x => x * 3;
const composeF = (f, g) => x => f(g(x));
const add1ThenMul3 = composeF(mul3, add1);
add1ThenMul3(10); // 33
上方的组合函数非常的简单,它接受两个参数f和g,并返回一个新函数,在调用f和g函数后返回结果。我们使用composeF 结合 add1和mul3来形成新的函数 add1ThenMul3.
现在我们假设:
1. 我们有两个文件 file1和file2, file2中包含我们想要的内容,而file2的路径在file1中, 已知file1的路径。
2. 写一个读文件的function.
// 1. 根据file1的路径获得file1文件中的内容, 并返回
// 2. 根据file2的路径获得file2文件中的内容,并返回
// 3. file2的路径是读取file1返回的
const composeF = (f, g) => x => f(g());
const readFileSync = path => fs.readFileSync(path.trim()).toString();
const readFileContentSync = composeF(readFileSync, readFileSync);
readFileContentSync('./file1'); // file2的内容
readfilesync是一个同步io函数,它将路径字符串作为输入并返回字符串中给定路径的内容。
所以可以用componseF来编写想到的函数;
但是如果readfile 是一个异步的过程,那么可以用我们node.js中的回调来进行流控制,这种方式有个正式的名字:continuation-passing-style 或者CPS。
readFile 函数将变为如下:
const readFileCPS = (path, cb) => {
fs.readFile(path.trim(), (err, data) => {
const result = data.toString();
cb(result);
});
}
此时,随着readFile函数的变化,composeF已经不能够正确返回结果了,所以我们需要重写合成函数:
const composeCPS = (g, f) => {
return (x, cb) => {
g(x, y => {
f(y, z => {
cb(z);
})
})
}
}
// 使用新的合成函数
const readFileContentCPS = composeCPS(readFileCPS, readFileCPS);
readFileContentCPS('./file1', result => console.log(result));
注意:重写的composeCPS的参数顺序被调换了,函数会先调用g 然后调用f,最终cb返回值。
接下来,再做一些有趣的事情
首先,调整readFileCPS的函数声明为readFileHOF, HOF的含义是 高阶函数 [high order function]
const readFileHOF = path => cb => {
readFileCPS(path, cb);
}
重新定义compose函数 composeHOF:
const composeHOF = (g, f) => {
return x => cb => {
g(x)(y => {
f(y)(cb);
})
}
}
const readFileContentHOF = composeHOF(readFileHOF, readFileHOF);
readFileContentHOF('/file1')(result => console.log(result));
继续改进readFileHOF函数:
const readFileEXEC = path => {
return {
exec: cb => readFileCPS(path, cb)
}
}
继续改进compose函数 composeEXEC:
const composeEXEC = (g, f) => {
return x => {
return {
exec: cb => {
g(x).exec(y => {
f(y).exec(cb)
})
}
}
}
}
const readFileContentEXEC = composeEXEC(readFileEXEC, readFileEXEC);
readFileContentEXEC('/file1').exec(result => console.log(result));