-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Proposal: add a built-in reduce
function on the type level
#12512
Comments
Edit: I now realized more of these than I thought can currently be covered using those walls of increasingly long definition lines with increasing amounts of generics. For those this proposal would just offer syntactic sugar for definition writers. Later edit: having tried those walls of overloaded definitions now, I think I can safely conclude they're not a viable alternative. Performance somehow gets terrible, while |
Note that one way to achieve this would be to enable type-level array iteration, in which case this proposal for a custom solution would be obsolete. That'd probably an elegant approach, as it'd be more general than just this (though Ways to achieve this:
|
Update: I've managed to emulate Missing pieces:
Presuming we can tackle those, we should no longer need a hard-coded |
This is my first proposal here, so feedback welcome.
TL;DR I noticed arrays in the standard lib are assumed homogeneous e.g.
Array<T>
, I'd like to see better support for heterogeneous arrays (i.e. tuples), and in looking for solutions there concluded being able to usereduce
in the type language might help a lot to facilitate hard typings, that is, cut down on walls of generics to get them into one line.To prevent confusion, I started looking for a solution to typing reduce-like functions, but ended up finding a way using a
reduce
-like function (in the type language) so as to solve something more general.Array.prototype.reduce
definitions currently naively assume the initial value, output, and all intermediate values share one and the same type, all following the 'homogeneous array of unspecified length'Array<T>
presumption:This assumption may hold in quite some cases, though not in all. The reality for those cases may be harder to catch in the current typing system, as each successive result is really based on what came before it. I believe to properly capture this the typing system would need to be able to iterate just as the original function would (though dealing with types rather than values).
Code illustrating the issue:
Expected behavior:
Expected inferred type: number
Actual behavior:
Reduce concludes object in, object out.
Relevance:
I hear you asking.
Well, out of the functions contained in Ramda, I'm under the impression the following functions utilize this
reduce
type of logic, and whose typings I think would benefit from solving this problem:reduce
,reduceBy
,scan
path
,pathEq
,pathOr
,pathSatisfies
,assocPath
,dissocPath
,lensPath
**invertObj
**,invert
mergeAll
,mergeWithKey
,mergeWith
(though notmerge
)pickBy
(infer which to pick, types if heterogeneous)fromPairs
,toPairs
,toPairsIn
**where
,whereEq
(relevant if calculating the result on the type-level, which requires all values at compile-time, forwhere
functions returning actual boolean values e.g. type guards, forwhereEq
a value equality check)flatten
*indexBy
sequence
,transduce
,traverse
applySpec
filter
(for objects this allows typing properly rather than as a Partial given known boolean outcomes, as is possible using type guards)xprod
*zipObj
*, **reverse
*, **pipe
,pipeP
,pipeK
**compose
,composeP
,composeK
**curry
? ***
: added value presuming heterogeneous content (tuple input).**
: currently the case for pipe/compose/curry, and in retrospect for the others marked this way (including ironically thepath
I'd taken for my example), these can be worked around using lists covering the different possible sizes (squared in case ofxprod
), which works, though it makes definition files harder to read/write/maintain (esp. when all functions are curried as well). This would for those cases turn this proposal into no more than syntactic sugar to turn all those predictable variants into one line.I ignored silly ones like
range
/times
that'd technically get more type info (-> array size known) but wouldn't plausibly benefit from it.I might be off by a bit, but this would seem to be in the order of 37 functions (/234), including ones I've yet to use, but some others I'd deem indispensable.
Summarizing:
Now, I'd wondered if we needed some generic construct for iteration on the type level, but that might raise a whole slew of new questions (what would this look like syntax-wise? where else might it be used? wouldn't the type language become a copy of the actual language?). That's not the way I'd like to go here.
Information we have:
reduce
pattern (the non-trivial version, i.e. cases where the current typing system fails).Proposal:
I believe functional programming in TypeScript could be significantly improved by exposing a reduce function on the type level.
Note:
Array.prototype.reduce
, I suppose ideally with the array as the third function argument, e.g.function reduce(callback /* pars: (accumulator, currentValue, currentIndex) */, initialValue, values /* Array, don't even bother running if info like length is unknown */)
.Array.prototype.reduce
, this would instead deal with types rather than values.reduce
(good/bad because it seems like a regular function) to_RDC
(looks more like a type-level name, unlikely to clash, still kinda recognizable, but ugly).Examples:
Using
_RDC
as a name for example purposes, we can retry the example from above:But we can do more than that.
The bigger picture here would be to also fix all of the Array methods from the standard library, so let's start looking at a simple example from there, reversing a tuple.
A function version might look like this:
Can we reduce that?
Let's look at a definition of compose, here with 1 param on the outer function:
Can we generalize this? I think so.
We mentioned these all use 1 input param. What does varying that part look like?
Can we incorporate that?
The opposite (
R.pipe
) is easier, no flipping:How about
curry
, as given by sandersn in his Variadic Kinds proposal? In there, he gaven an example for the case of dividing the parameters over 2 function applications:curry<...T,...U,V>(f: (...args:[...T,...U]) => V, ...ts:...T): (...us: ...U) => V
. Let's try to make that more general:Let's try to reduce.
This appears to be covering the case where arguments not passed immediately would need to be passed in one by one (so using a full wall as above is still more useful), but it's a start.
To revisit those
reduce
definitions from the standard library, these could then be fixed up with a list of generic tuple definition for various lengths:Note that these would also need to capture the case of homogeneous arrays, since info on array size (and separate keys) is relevant. Note that the maximum tuple length may this way become a bottleneck for the definition of the method version.
So, would you have these definitions generate the original definition walls?
I think if from TS ->
.d.ts
we'd generate the 'resulting' possible types (i.e. the list options[A]
, [A,B], [A,B,C]
, ...), we'd have to choose a balance between (1) limiting to a fixed number vs. (2) keeping our files clean. This sounds like a lose-lose trade-off, so I'd either generate options in-memory or have TS calculate them as needed.Language Feature Checklist:
SYNTAX:
Since from a definition writer's perspective this feels intuitively similar as using 'regular' functions (function application:
F(T)
) on the type level, I'd be inclined to have this use the same syntax.Since this is on the type level and using an existing syntax, I expect no clashes with ES.
SEMANTICS:
any
).EMIT:
any
as well in this case -- all required info must be inferable for this to add value.COMPATIBILITY:
No breaking changes.
OTHER:
Array<T>
).P.S.: I'm also hoping for improvement for
map
. I think these two form the basis for a lot of the basic FP operations, and with these out of the way FP in TS should become a lot more pleasant.The text was updated successfully, but these errors were encountered: