-
-
Notifications
You must be signed in to change notification settings - Fork 94
foldable: add S.takeWhile and S.dropWhile #356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
foldable: add S.takeWhile and S.dropWhile #356
Conversation
|
I put these two functions near |
index.js
Outdated
| [Fn(a, $.Boolean), m(a), m(a)], | ||
| Z.filterM); | ||
|
|
||
| //# takeWhile :: (Foldable f, Monoid (f a)) => (a -> Boolean) -> f a -> f a |
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.
Nit: There's an extra space after the =>.
index.js
Outdated
|
|
||
| //# takeWhile :: (Foldable f, Monoid (f a)) => (a -> Boolean) -> f a -> f a | ||
| //. | ||
| //. Returns the first elements for which the predicate returns true. |
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 doesn't sound quite right to me for a non-array argument. How about the following?
Discards the first inner value which does not satisfy the predicate, and all subsequent inner values.
index.js
Outdated
| //. | ||
| //. ```javascript | ||
| //. > S.takeWhile(c => c < 'c', ['a', 'b', 'c', 'd', 'e']) | ||
| //. ['a', 'b'] |
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.
c => c < 'c' is slightly confusing due to c being used in two ways. n => n < 3 with [1, 2, 3, 4, 5] would be clearer. Even better, though, would be to use an example which demonstrates how this differs from filter:
> S.takeWhile(S.odd, [3, 3, 3, 7, 6, 3, 5, 4])
[3, 3, 3, 7]
index.js
Outdated
| //. Just(1) | ||
| //. | ||
| //. > S.takeWhile(S.K(false), S.Just(1)) | ||
| //. Nothing |
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.
Why not use a predicate which references its argument?
> S.takeWhile(S.odd, S.Just(1))
Just(1)
> S.takeWhile(S.odd, S.Just(2))
NothingI don't think we should include these examples, though, as they would be better expressed with filter.
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 was trying to show an example of using a non-array. If we're not allowing strings, I can't think of a good example.
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 okay to provide array examples only.
index.js
Outdated
| function takeWhile(p, xs) { | ||
| if (Array.isArray(xs)) { | ||
| var result = []; | ||
| if (xs.length === 0) return result; |
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.
Probably makes more sense to create the result array after checking the length of xs.
index.js
Outdated
| function dropWhile(p, xs) { | ||
| if (Array.isArray(xs)) { | ||
| if (xs.length === 0) return []; | ||
| for (var i = 0; i < xs.length && p(xs[i]); i += 1); |
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 idx is preferred over i.
index.js
Outdated
| //. > S.takeWhile(S.K(false), S.Just(1)) | ||
| //. Nothing | ||
| //. ``` | ||
| function takeWhile(p, xs) { |
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 :%s/\<p\>/pred/gc for consistency with existing code.
index.js
Outdated
| if (Array.isArray(xs)) { | ||
| var result = []; | ||
| if (xs.length === 0) return result; | ||
| for (var i = 0, x; i < xs.length && p(x = xs[i]); i += 1) result.push(x); |
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 :%s/\<i\>/idx/gc for consistency with existing 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.
Interestingly, I did a search on the code we have and neither of our for loops use idx.
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.
We don't currently have a single for loop which uses an index variable. :)
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.
Yeah, I guess they're all in sanctuary-type-classes now.
index.js
Outdated
| function takeWhile(p, xs) { | ||
| if (Array.isArray(xs)) { | ||
| var result = []; | ||
| if (xs.length === 0) return result; |
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 line is unnecessary.
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 is what I get for submitting a PR while on cold medicine 🤧
index.js
Outdated
| //. ``` | ||
| function dropWhile(p, xs) { | ||
| if (Array.isArray(xs)) { | ||
| if (xs.length === 0) return []; |
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 line is unnecessary.
index.js
Outdated
| } | ||
| var drop = true; | ||
| function dropWhileReducer(xs, x) { | ||
| return drop && p(x) ? xs : (drop = false, append(x, xs)); |
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.
Is this a bit clearer, do you think?
return (drop = drop && p(x)) ? xs : append(x, xs);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.
Not sure if a good idea, but could we borrow generators' nomenclature and go with done here? In that case this reads clearer to me:
// takeWhile
return !done && p(x) ? append(x, xs) : (done = true, xs);
// dropWhile
return !done && p(x) ? xs : (done = true, append(x, xs));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 @cust0dian has the clearest statement using done and the assignment in the binary expression at the end. done = true is very explicit.
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 like the use of done. :)
test/takeWhile.js
Outdated
| eq(S.takeWhile(lt(3), [1, 2, 3, 4, 5]), [1, 2]); | ||
| eq(S.takeWhile(lt(4), [1, 2, 3, 4, 5]), [1, 2, 3]); | ||
| eq(S.takeWhile(lt(5), [1, 2, 3, 4, 5]), [1, 2, 3, 4]); | ||
| eq(S.takeWhile(lt(6), [1, 2, 3, 4, 5]), [1, 2, 3, 4, 5]); |
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 change these to demonstrate and assert that takeWhile is different from filter.
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 fine. I'll have to keep the tests using Maybe to maintain code coverage.
ec3176a to
1a31f4f
Compare
|
This should be ready to go. |
index.js
Outdated
| idx < xs.length && pred(x = xs[idx]); | ||
| idx += 1) result.push(x); | ||
| return result; | ||
| } |
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 move this to a separate function:
function Array$takeWhile(pred, xs) {
...
}
function takeWhile(pred, xs) {
if (Array.isArray(xs)) return Array$takeWhile(pred, xs);
...
}
S.takeWhile =
def(...);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.
Could we use xs.slice(idx) rather than push repeatedly?
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'd prefer Array$takeWhile to a generic findIndex? The definition of Array$takeWhile and Array$dropWhile will be nearly identical save the arguments to slice.
We don't have to expose 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.
Plus we can just remove it once support for IE is dropped...in 2025 😜
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 took the liberty of going the findIndex route. I can change it if you feel strongly.
index.js
Outdated
| [Fn(a, $.Boolean), m(a), m(a)], | ||
| Z.filterM); | ||
|
|
||
| //# takeWhile :: (Foldable f, Monoid (f a)) => (a -> Boolean) -> f a -> f a |
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 we should replace Monoid (f a) with Alternative f for the reason given in sanctuary-js/sanctuary-type-classes#37.
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.
btw here Alternative f constraint is used to express partiality
https://github.com/guaraqe/total-alternative#readme
https://github.com/guaraqe/total-alternative/blob/master/src/Data/List/Alternative.hs
https://github.com/guaraqe/total-alternative/blob/master/src/Data/Foldable/Alternative.hs
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 we updated the type signature without switching from Z.empty to Z.zero. I'll rectify this in the pull request I'll soon open for sanctuary-type-classes.
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.
Actually, given our concat-based implementation I think Monoid (f a) is the correct constraint after all. We could have a chain-based takeWhileM (to match filterM) but it seems excessive to provide two versions of a function which is only used occasionally.
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.
1a31f4f to
19e8fb3
Compare
index.js
Outdated
| function Array$findIndex(pred, xs) { | ||
| for (var idx = 0; idx < xs.length && pred(xs[idx]); idx += 1); | ||
| return idx; | ||
| } |
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 would expect Array$findIndex(x => x === 42, [1, 2, 3]) to evaluate to -1 rather than to 0. It seems this function isn't quite Array#findIndex after all.
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.
My mistake. Even returning -1 it would still be incorrect. I was naming it findIndex but I can't really think of a good name for it...findFirstFailingIndexMinusOne.
I'll make Array$takeWhile and Array$dropWhile instead.
19e8fb3 to
04df4f5
Compare
index.js
Outdated
| //. [] | ||
| //. ``` | ||
| function Array$takeWhile(pred, xs) { | ||
| for (var idx = 0; idx < xs.length && pred(xs[idx]); idx += 1); |
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.
My preference is to only declare block-scoped variables in loops (even though we don't actually have block scoping without let). Since we refer to idx outside the loop I'd like to declare it outside the loop:
var idx = 0;
for (; idx < xs.length && pred(xs[idx]); idx += 1) {}It would then make sense to switch to a while loop, as it would remove the awkward empty block:
var idx = 0;
while (idx < xs.length && pred(xs[idx])) idx += 1;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 empty block is unnecessary. It can be replaced by a semicolon. But I'll make the change nonetheless.
|
I left one final comment, @gabejohnson. Please also rebase your branch. Let me know once you have done so (as I don't receive a notification when you |
04df4f5 to
951a7e7
Compare
|
@davidchambers It should be good to merge now. |
Closes #349