-
Notifications
You must be signed in to change notification settings - Fork 27
Conversation
process.stderr.write(String(err) + '\n'); | ||
process.exit(1); | ||
}); | ||
concatFiles(join(process.argv[2])).then(exit0, exit1); | ||
}; | ||
|
||
if (process.mainModule.filename === __filename) main(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may be interested in these changes, @zhangchiqing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice refactor!
For the concatFiles
function, it's very clean to chain all the sub functions together. However I'm afraid it would be a bit hard for learners to read.
I'd like to add some intermediate variables and type signatures so that it's clear what each step returns.
And this style also highlights that the logic reads as if it's imperative and sync, but is actually functional and async.
// concatFiles :: (String -> String) -> Promise Error String
const concatFiles = path => {
// String -> Promise Error String
const readFileFromPath = S.compose(readFile, path);
// Promise Error String
const indexFileP = readFileFromPath('index.txt');
// Promise Error [String]
const fileNamesP = P.liftp1(S.lines)(indexFileP);
// Promise Error [String]
const filesP = P.liftp1(P.traversep(readFileFromPath)(fileNamesP);
// Promise Error String
return P.liftp1(S.joinWith(''))(filesP);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback, @zhangchiqing. I pushed an update. Let me know what you think of it. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Thanks @davidchambers 👍
concatFiles(join(process.argv[2])).fork(exit1, exit0); | ||
}; | ||
|
||
if (process.mainModule.filename === __filename) main(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this file look good to you, @Avaq?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅
kgo.js
Outdated
('files', ['filePaths'], S.curry3(foreign.parallel, readFile)) | ||
('concated', ['files'], kgo.sync(S.joinWith(''))) | ||
(['concated'], exit0) | ||
(['*'], exit1); | ||
}; | ||
|
||
if (process.mainModule.filename === __filename) main(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may be interested in these changes, @KoryNunn.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly I'm not advocating for kgo's usage these days, but rather suggest people look at righto. I'd be happy to drop this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. Done. :)
lazy-either.js
Outdated
// | ||
// We cannot use S.traverse because LazyEither does not currently support | ||
// recent versions of the Fantasy Land specification. It's thus necessary | ||
// to dispatch to `map` and `ap` (rather than their prefixed equivalents). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you still maintaining LazyEither, @Risto-Stevcev? If so, have you plans to support fantasy-land/
-prefixed methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's implementing the fantasy land spec, what are the fantasy-land prefixed methods?
It's not actively developed but it's lazily maintained (accepting PRs). Avaq's fluture lib handles the basic cases for using Either and Maybe with futures so I recommend that to people. Due to LazyEither's restrictions, it does have some interesting properties that fluture can't have because it's more general, but I'm more interested in purescript right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's implementing the fantasy land spec, what are the fantasy-land prefixed methods?
Methods have required the fantasy-land/
prefix since fantasyland/fantasy-land#146 which was released in v1.0.0. The current version is v3.2.0, so LazyEither is a little out of date. I'll submit a pull request to prefix the methods. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove the LazyEither solution, Risto. As I stated in Risto-Stevcev/lazy-either#2, Fluture and LazyEither are duplicative. There's little value in including both here.
process.stderr.write(String(err) + '\n'); | ||
process.exit(1); | ||
}); | ||
concatFiles(join(process.argv[2])).then(exit0, exit1); | ||
}; | ||
|
||
if (process.mainModule.filename === __filename) main(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may be interested in these changes, @Risto-Stevcev.
concatFiles(join(process.argv[2])).then(exit0, exit1); | ||
}; | ||
|
||
if (process.mainModule.filename === __filename) main(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may be interested in these changes, @paldepind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice 😄 Why did you change then(Promise.all)
to then(Promise.all.bind(Promise))
? It should work fine without the bind I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I switched from Bluebird promises to native promises, which require context binding.
process.exit(0); | ||
} | ||
concatFiles(join(process.argv[2]))((err, data) => { | ||
if (err == null) exit0(data); else exit1(err); | ||
}); | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may be interested in these changes, @KoryNunn.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change to remove variables for each logical step goes violently against the grain of righto, and makes this, to my eyes, unreadable.
Also the change to use sanctuary within this example in my opinion muddies it, since a new viewer would have to look up how it works AND how righto works.
At this point, since all the examples use sanctuary, I don't see this as a useful comparison, but a contrived example that is designed explicitly to show of one or two particular examples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the implementation. Is this more to your liking?
// concatFiles :: (String -> String) -> Righto String
const concatFiles = path => {
const readFileRel = S.compose(readFile, path);
const index = readFileRel('index.txt');
const files = righto.sync(S.compose(S.map(readFileRel), S.lines), index);
return righto.sync(S.joinWith(''), righto.all(files));
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's definitely much more like how it is intended to be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. Thank you for your feedback, Kory. :)
futures.js
Outdated
readFile, // :: Future Error String | ||
S.map(S.lines), // :: Future Error (Array String) | ||
S.map(S.map(path)), // :: Future Error (Array String) | ||
S.chain(S.traverse(Future, readFile)), // :: Future Error (Array String) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's important to read all files in parallel, we can replace this line with one of two versions:
- Using
traverse
over ConcurrentFuture (Par) structure:
S.chain(S.compose(Future.seq, S.traverse(Future.Par, S.compose(Future.Par, readFile))))
- Using
Future.parallel
:
S.map(S.map(readFile)),
S.chain(Future.parallel(Infinity))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading files in parallel would be very nice indeed! Which version do you favour, and why? I'm happy to follow your recommendation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It saddens me* to prefer the second version, because it's more concise and it's clearer what's going on. It's also more similar to many of the Promise based solutions in the project.
* It would be nicer to be able to utilize traverse
for this purpose without the verbose type juggling. If we would go for the first approach, some ways to make it more readable would be to name S.compose(Future.Par, readFile)
something like concurrentReadFile
, and maybe use map
to avoid the nested compose
:
const concurrentReadFile = S.compose(Future.Par, readFile);
//...
S.map(S.traverse(Future.Par, concurrentReadFile)), // :: Future Error (ConcurrentFuture Error (Array String))
S.chain(Future.seq) // :: Future Error (Array String)
I'll leave it up to you to decide which of the three versions you deem most appropriate. I can't decide.
common/sanctuary.js
Outdated
x => S.type(x) === Future.Par['@@type'], | ||
f => [], | ||
f => [] | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this look right to you, @Avaq? It seems the ConcurrentFuture type lacks helpers corresponding to those that exist for defining FutureType :: Type -> Type -> Type
. I'd love to be able to do this:
const Future = require('fluture');
const $ = require('sanctuary-def');
const {ConcurrentFutureType, FutureType} = Future.defineTypes($);
Fluture could then provide type definitions without depending on sanctuary-def (although it would be a good idea to specify sanctuary-def as a peer dependency). What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd love to be able to [use something like
Future.defineTypes($)
]
common/sanctuary.js
Outdated
'https://github.com/fluture-js/Fluture#concurrentfuture', | ||
x => S.type(x) === Future.Par['@@type'], | ||
f => [], | ||
f => [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose you could use compose(Future.extractLeft, Future.seq)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡
futures.js
Outdated
@@ -12,7 +12,7 @@ const S = require('./common/sanctuary'); | |||
|
|||
// readFile :: String -> Future Error String | |||
const readFile = | |||
S.compose(Future.node, S.curry3(fs.readFile, S.__, {encoding: 'utf8'})); | |||
S.curry3(Future.encaseN2, fs.readFile, S.__, {encoding: 'utf8'}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the more concise S.flip(Future.encaseN2(fs.readFile), {encoding: 'utf8'})
, why did you choose this option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the placeholder clearer than flipping in this case. I'm happy to change it, though.
await.js
Outdated
Promise.all(S.map(readFileRel, S.lines(index))) | ||
.then(S.joinWith('')) | ||
.then(exit0, exit1); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this file be structured the same way the coroutine
-files are?
// concatFiles :: (String -> String) -> Promise Error String
async function concatFiles(path){
const index = await readFile(path('index.txt'));
const filenames = S.map(path, S.lines(index));
const results = await Promise.all(S.map(readFile, filenames));
return S.joinWith('', results);
}
const main = () => {
concatFiles(join(process.argv[2])).then(exit0, exit1);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡
.map(S.map(path)) | ||
.map(S.map(readFile)) | ||
.map(Promise.all.bind(Promise)) | ||
.awaitPromises() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's weird to use Promise.all
followed by awaitPromises
to await a single Promise in the context of streaming.
We could use most.from
to create a stream of multiple promises, and then await all of them. That also avoids the joining of an array of strings followed by the joining of a single string to an empty string.
// concatFiles :: String -> Promise Error String
const concatFiles = path =>
most.fromPromise(readFile(path('index.txt'))) // :: Stream Error String
.map(S.lines) // :: Stream Error (Array String)
.map(S.map(path)) // :: Stream Error (Array String)
.map(S.map(readFile)) // :: Stream Error (Array (Promise Error String))
.chain(most.from) // :: Stream Error (Promise Error String)
.awaitPromises() // :: Stream Error String
.reduce(S.joinWith(''), '') // :: Promise Error String
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems much better (although the reducing function should be S.concat
rather than S.joinWith('')
). When I try it in one of the failure cases, though, I see:
UnhandledPromiseRejectionWarning: Unhandled promise rejection
DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes - I got confused. I think S.joinWith('')
is equal to reduce(S.concat, '')
.
Do you know which Promise is failing? The index.txt
or one of the others?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know which Promise is failing?
No. Determining this will require investigation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that the Promise settles before Most attaches handlers. In order to make sure handlers are attached, we use most.fromPromise
immediately on the return value from readFile
:
// readFileStream :: String -> Stream Error String
const readFileStream = S.compose(most.fromPromise, readFile);
// concatFiles :: (String -> String) -> Promise Error String
const concatFiles = path =>
most.fromPromise(readFile(path('index.txt'))) // :: Stream Error String
.map(S.lines) // :: Stream Error (Array String)
.map(S.map(path)) // :: Stream Error (Array String)
.map(S.map(readFileStream)) // :: Stream Error (Array (Stream Error String))
.chain(most.mergeArray) // :: Stream Error String
.reduce(S.concat, '') // :: Promise Error String
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This version handles the failure cases but no longer guarantees order in the success case. I'd love to see an updated version, Aldwin. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep the solution as is, I'm no frequent most.js
user and cannot easily solve the "Promise settles before Most attaches handlers" problem. It's essentially just another Promise flaw - running into these issues really makes me appreciate Fluture.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these changes are an overall improvement in their current form. I will have a quick look into the most.js solution.
EDIT: All tests are passing on this branch. The UnhandledPromiseRejectionWarning
only occurs on master.
EDIT2: I tested with Node 6 and Node 4 as well as Node 7, for good measure. The most.js
tests are passing in all versions.
EDIT3: Ignore all of the above. Here's the fix
I'm about to squash the commits. The original commits can be found at |
e8e5d4f
to
b4986db
Compare
It's been a while since anyone gave this project love and attention, so here's some ❤️.
Changes:
exit0
,exit1
,join
, andreadFile
--harmony
flagsawait