码迷,mamicode.com
首页 > 其他好文 > 详细

[Functional Programming] mapReduce over Async operations with first success prediction (fromNode, alt, mapReduce, maybeToAsync)

时间:2019-03-10 09:21:45      阅读:142      评论:0      收藏:0      [点我收藏+]

标签: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, alt, mapReduce, maybeToAsync)

标签:hat   operation   The   method   throw   ant   help   some   work   

原文地址:https://www.cnblogs.com/Answer1215/p/10504050.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!