Skip to content
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

Consider changing minWith and maxWith APIs to be more like sortBy #65

Closed
CrossEye opened this issue Jun 21, 2014 · 29 comments
Closed

Consider changing minWith and maxWith APIs to be more like sortBy #65

CrossEye opened this issue Jun 21, 2014 · 29 comments
Assignees

Comments

@CrossEye
Copy link
Member

sortBy has a nicer API than do minWith and maxWith.

In sortBy we generate a key for the objects to sort, and the comparator to use is automatically created based on this key. So we can do something as simple as

sortBy(prop('title'), list);

minWith and maxWith, by contrast, which don't even need to be sorted, require you to supply a comparator.

minWith(function(a, b) {return a.x - b.x;}, points)

This really should be

minWith(prop('x'), points);

Granted the current API offers some theoretic additional flexibility. But I can't see any realistic cases that would take advantage of it.

If there are no objections, I'll do this over the weekend.

@CrossEye CrossEye self-assigned this Jun 21, 2014
@buzzdecafe
Copy link
Member

+1

@CrossEye
Copy link
Member Author

Fixed in c1c986b

@megawac
Copy link
Contributor

megawac commented Jun 22, 2014

I think this should this be done with uniqWith, differenceWith, et. al. as well

@buzzdecafe
Copy link
Member

i'm changing my mind on this. what if the objects in the list you are evaluating have complicated relations? e.g. uniqWith(function(a, b) { return a.x === b.y && a.y === b.y; }, list) does not get captured using this approach. Then the simple prop implementation of the *With functions is a special case of passing a more abstract comparator.

@buzzdecafe buzzdecafe reopened this Jun 22, 2014
@CrossEye
Copy link
Member Author

EDIT: this was in response to @megawac's comment, not @buzzdecafe's.

I'm not so sure about that. I can easily see:

MyType.areEqual(a, b) {
    if (!(a instanceof MyType && b instanceof MyType)) {return false;}
    if (a.hash() !== b.hash()) {return false;}
    if (a.testDifficultProperty1() !== b.testDifficultProperty1()) {return false;}
    if (a.testDifficultProperty2() !== b.testDifficultProperty2()) {return false;}
    // ...
    return true;
}

uniqWith(MyType.areEqual, listOfMyType)

This can't necessarily be captured by a single key.

@megawac
Copy link
Contributor

megawac commented Jun 22, 2014

Some other libraries allow you to return an array from these functions which will compare in order.

@CrossEye
Copy link
Member Author

@buzzdecafe: I agree regarding uniqWith, et al. But I can't see any use cases for minWith / maxWith, although maybe we should rename to minBy / maxBy ?

@buzzdecafe
Copy link
Member

i was also thinking *By for single-property comparison

@CrossEye
Copy link
Member Author

@buzzdecafe I'm also not sure that your example makes real sense, as I think all of these comparisons really need to be symmetric in the two parameters. But the general point stands.

But yes, use *By for single-property comparisons, whether a natural property of the object or a synthesized one, *With for a more general function. That would be a cleaner API.

@buzzdecafe
Copy link
Member

I think the objection stands for minWith on the same grounds. You can have a list of objects that have some crazy rules for determining which one is the smallest that is not captured in an attribute. Imagine there is some kind of pecking order ...

@CrossEye
Copy link
Member Author

@megawac Don't understand about the array. I'm mostly ok with composeing reverse if necessary. How does returning an array help?

@megawac
Copy link
Contributor

megawac commented Jun 22, 2014

Would give you the same functionality from a *By function as a *With

ramda.maxBy(function(x) {
   return [x.x, x.y]
}, [{x: 1, y: 2}, {x: 1, y: 1}, {x: 1, y: 5}, {x: 1, y: 2}]); //=> {x: 1, y: 5}

@buzzdecafe
Copy link
Member

@CrossEye. that is essentially the way i implemented the path* functions. In that case, should the sig of *By be Str -> [Obj] -> Obj? and the inital Str gets converted to prop(Str)?

@megawac ok, that's interesting, but does not address the more complex example @CrossEye suggested

@CrossEye
Copy link
Member Author

@megawac Got it; thought you were talking ascending vs descending. I'd rather not go there. If we get as complicated as that, I'd rather just let 'em use an arbitrary function.

@megawac
Copy link
Contributor

megawac commented Jun 22, 2014

Alright, it might be worth creating a *By generator and a *With generator and allowing users to decide regardless (because personally I hate having to work with a and b all the time [begins to look confusing])

@CrossEye
Copy link
Member Author

@buzzdecafe I like a more general *By for these. The key could still be entirely synthetic:

var makeSortName = fork(join(' '), prop('lastName'), always(','), prop('firstName'));
maxBy(makeSortName, people);

(untested, and don't know if we actually have join, but should be clear.)

@CrossEye
Copy link
Member Author

@megawac I agree, and it should be easy to implement the *By in terms of the *With.

@buzzdecafe
Copy link
Member

@CrossEye that example seems like a maxWith to me. What is the distinction of By and With in your mind?

btw, gotta come up with a new name for fork

@CrossEye
Copy link
Member Author

@buzzdecafe *By focuses on a single list item, generates a key for it. *With is the one that knows how to do the comparison between two different items. That was the issue that started me down this trail. There was no good reason to be writing a full-fledged comparator when I often wanted to either fetch or synthesize something from a single object.

@buzzdecafe
Copy link
Member

at present pathBy takes a string as its first param. I'd like all of the *By fns to have the same semantics, and passing a function is overkill to pathBy. And I also don't wanna type-check the first arg

@CrossEye
Copy link
Member Author

Yeah, that's a different beast, isn't it? I've been thinking about max/min/sort/uniq/difference, which I think could all share these semantics. But path is definitely different.

@megawac
Copy link
Contributor

megawac commented Jun 22, 2014

I disagree @buzzdecafe, I think a lookupIterator function would be sweet for choosing how to use the given argument

@CrossEye
Copy link
Member Author

@megawac Yes, the trouble is that it would be nice if we could have a universal semantic for the *By and *With, which just got different meanings on the new path.

@megawac
Copy link
Contributor

megawac commented Jun 22, 2014

It wouldn't be hard to mix that in with an iterator

@buzzdecafe
Copy link
Member

since pathBy seems to be the problem child here, maybe the simplest solution is to come up with a new name for it.

@buzzdecafe
Copy link
Member

let's rename pathBy to pathOn and use With and By semantics elsewhere as described above.

@CrossEye
Copy link
Member Author

@megawac: We've been trying to avoid any unnecessary type-checking, making our functions as strongly-typed as feasible in JS. We definitely aren't interested in the Underscore/LoDash style of "if parameter 3 is a function do x, if it's an object, do y, and if it's a string do z." This is not to say that we have no type-checking, but there's relatively little, and that's mostly to support dynamic dispatch for extensions, such as the lazy lists, and (soon, I hope) the algebraic types.

@buzzdecafe That might be best. But can't think of a good name.

@CrossEye
Copy link
Member Author

@buzzdecafe That sounds good.

@CrossEye
Copy link
Member Author

The original goal of this issue was long ago satisfied. If we want to follow up on the remaining discussion, we can reopen later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants