[Functional Programming] mapReduce over Async operations with first success prediction (fromNode, al
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Functional Programming] mapReduce over Async operations with first success prediction (fromNode, al相关的知识,希望对你有一定的参考价值。
?????????hat operation The method throw ant help some work
Let???s say we are going to read some files, return the first file which pass the prediction method, this prediction method can be just check whether the file content contains more than 50 chars.
For reading the file, it has tow requirements, first we should have the right to read the file, then read file content, we can use Node.js method:
fs.access
fs.readFile
We won???t directly using those methods, we are going to wrap those functions into Async functor:
const {Async, curry} = require(???crocks???); const {fromNode} = Async; const access = fromNode(fs.access); const readFile = fromNode(fs.readFile); const accessAsync = curry((mode, path) => access(path, mode) .map(constant(path))); // readFileAsync :: Option -> a -> Async Error b const readFileAsync = curry((option, path) => readFile(path, option));
By using ???fromNode???, we are able to conver the Node???s method into Async functor.
Here, we also put ???path??? to the last params and apply ???curry???, this is because we want to partially apply the params in the future.
Now ???accessAsync??? & ???readFileAsync??? both return ???Async??? type, we can compose them:
const {Async, constant, composeK, curry} = require(???crocks???); ... // loadTextFile :: String -> Async Error String const loadTextFile = composeK( readTextFile, checkRead );
???loadTextFile??? is the only method we want to be exported.
We also create a helper method to fork Async functor:
const fork = a => a.fork( console.log.bind(null, ???rej???), console.log.bind(null, ???res???) );
Full Code for funs.js:
const fs = require(???fs???); const {Async, constant, composeK, curry} = require(???crocks???); const {fromNode} = Async; const access = fromNode(fs.access); const readFile = fromNode(fs.readFile); const accessAsync = curry((mode, path) => access(path, mode) .map(constant(path))); // readFileAsync :: Option -> a -> Async Error b const readFileAsync = curry((option, path) => readFile(path, option)); const checkRead = accessAsync(fs.constants.F_OK); const readTextFile = readFileAsync(???utf-8???); // loadTextFile :: String -> Async Error String const loadTextFile = composeK( readTextFile, checkRead ); const fork = a => a.fork( console.log.bind(null, ???rej???), console.log.bind(null, ???res???) ); module.exports = { loadTextFile, fork }
Then let???s continue to build our main.js file:
Let???s say we have an array of filenames:
const data = [ ???text.txt???, ???text.big.txt???, ???notfound.txt??? ];
???text.txt??? & ???text.big.txt??? are existing files, and only ???text.big.txt??? can pass the predicate function:
const isValid = x => x.length > 50;
So with those in mind, let???s define what we want to do:
1. We want to map over each filename in the ???data??? array, read file content
2. For each content, we want to check against our ???isValid??? method.
3. If the checking pass, it???s done! output the content
4. If not pass the checking, we continue with next filename, repeat step No.1.
5. If all the filenames have gone though, no matching found, throw error.
6. If the list is empty, throw error.
7. If list is not empty but no matching file, and there is a not found filename, also throw error.
Step1-4 is a the main logic, step 5-7 is just some house keeping, throw some errors...
Step1-4 is prefect case for using ???mapReduce???
???mapReduce??? here means, we first mapping over each case, then we do ???reduce??? or let???s say ???concat???; ???mapReduce??? require a "empty" case, since our is Async functor, then the empty case will be a rejected async functor.
const {fork, loadTextFile} = require(???./funs.js???); const {Async, curry, safe, mapReduce, maybeToAsync} = require(???crocks???); const data = [ ???text.txt???, ???notfound.txt???, ???text.big.txt???, ]; const isValid = x => x.length > 50; const concatAlt = pred => (acc, curr) => acc.alt(curr) .chain(maybeToAsync(new Error(???not good!???), safe(pred))) const flow = curry(pred => mapReduce( loadTextFile, //map concatAlt(pred), // reduce Async.Rejected(new Error(???list is empty???)) //Seed )); fork(flow(isValid, data));
Let???s have a look ???concatAlt??? in more details:
const concatAlt = pred => (acc, curr) => acc.alt(curr) // If acc async is rejected, then check curr, otherwise continue to next step ???chain??? with the value of acc .chain(maybeToAsync(new Error(???not good!???), safe(pred))) // Async(Error String) --safe(pred)--> Async(Maybe(Error String)) --maybeToAsync--> Async(Async(Error String)) --chain--> Async(Error String)
???alt???: works as fallback option, only has effect when ???acc??? is falsy. Which means, if first two files cannot pass ???isValid??? checking, but third passing, then we are still good! Also means, if the first one is passing the check, then we are not going to continue with next two files.
Here we are also using natural transform, maybeToAsync, more detail check my another post.
以上是关于[Functional Programming] mapReduce over Async operations with first success prediction (fromNode, al的主要内容,如果未能解决你的问题,请参考以下文章
[Functional Programming ADT] Debug a Functional JavaScript composeK Flow