jashkenas / underscore

JavaScript's utility _ belt
https://underscorejs.org
MIT License
27.3k stars 5.53k forks source link

Feature Request: mean, median, sum #2942

Closed penguinsAreFunny closed 2 years ago

penguinsAreFunny commented 2 years ago

Hello,

I am not sure where to post a feature request as I am not used to Github yet. Is this the right place for feature requests?

I would like support for simple arithmetic operations like mean, median, sum (and perhaps some more) in underscore. Like this: https://gist.github.com/AndreasBriese/1670507

Some more suggestions: difference, relativeDifferences, minDifference, maxDifference, minRelativeDifference, maxRelativeDifference, meanDifference, meanRelativeDifference, ...

I would also like to suggest support for functions like numpy-python: Example: https://numpy.org/doc/stable/reference/generated/numpy.sum.html https://numpy.org/doc/stable/reference/routines.html

jgonggrijp commented 2 years ago

Thanks for reaching out @penguinsAreFunny! This is the right place for feature requests.

Underscore aims to stay mean and lean, so it includes only the most fundamental functions that nearly every application needs. For all other functions, we have Underscore-contrib, an open-ended extension library where people can contribute "ad libitum". It so happens that somebody already made a start at contributing these and a few other statistical functions to contrib over here: https://github.com/documentcloud/underscore-contrib/pull/243. If you want, you can take off where @kpkalwa left it.

For historical reference: @AndreasBriese himself made the same proposal in #441.

shtse8 commented 2 years ago

@jgonggrijp Sorry to ping you back here again. Beside median, I think sum, mean are very fundamental and commonly and wisely use in coding, somethings like count. This is basis enough and not so advance to be statistical functions, even counting is also a part of statistic or a part of Maths, but we normally accept it is common in real life. More importantly, as using chain in underscore, without these fundamental methods provided making hard to chain. although we can add it through mixin, but it is failed to type in typescript. I understand it has been discussed in the past many times. But time is changing, while lodash providing these convenient methods but they are no longer maintaining and also thanks to rollup and es6 import, we can easily tree shaking unused operators, so adding more operators is not a big problem now.

I am migrating from lodash to underscore and without these basic math operators making me so confused. Hope it can add these basic maths methods. thanks for your contribution to this amazing project.

jgonggrijp commented 2 years ago

@shtse8 Thank you for reaching out; no need to apologize.

Unfortunately, once a function is added to Underscore through mixin, it can no longer be treeshaken. So if we were to add sum, mean, median etcetera to core Underscore and mix them in like all other functions, all users of the monolithic interface would be paying for their presence. In fact, this is one of the reasons why I'm considering to replace chain by a different mechanism that does support treeshaking in Underscore 2.0, perhaps pipe (#2809).

Even if treeshaking were possible, though, users who load the monolithic interface from a CDN would not benefit. So that is another reason to keep Underscore lean, which will persist even in an Underscore 2.0 without mixin.

Fortunately for you, TypeScript has module augmentation. Taking sum as an example, the module where you mixin your additions would look something like this (untested):

import {
    mixin,
    UnderscoreStatic,
    Underscore,
    _Chain,
    _ChainSingle,
    Collection,
    TypeOfCollection
} from 'underscore';
import { sum } from './your/own/code.ts';

// The regular JavaScript part
mixin({sum});

// The keeping TS happy part
declare module 'underscore' {
    // Unfortunately, due to the way @types/underscore is currently setup,
    // you always need to extend three interfaces. Check the source in the
    // DefinitelyTyped project for implementation details and examples.

    // Interface #1 - declaring the function in its "standalone" form
    interface UnderscoreStatic {
        // This part depends a bit on the exact interface of your `sum` function.
        sum<V extends Collection<any>>(collection: V): TypeOfCollection<V>;
    }

    // Interface #2 - declaring the function in OOP form
    interface Underscore<T, V = T[]> {
        // This part again depends on the exact interface of your function
        sum(): T;
    }

    // Interface #3 - declaring the function in chaining form
    interface _Chain<T, V = T[]> {
        // Again, depends on your function's exact interface
        sum(): _ChainSingle<T>;
    }
}

Admittedly, this is much more trouble for you than if sum were a part of core Underscore. Honestly, I think TypeScript is the one to blame here.