Reputation: 974
I'm trying to write point free curried function in typescript. And doing so i do find an issue.
Typescript seems to be immediately evaluating type, and, in my understanding, do not allow very well curried function.
Let me illustrate by an example, first with non curried function
const fn = (x: number): string => null
function inline <T, U>(fn: (x: T) => U, src: T[]): U[]
function inline <T, U>(fn: (x: T) => U, src: Iterable<T>): Iterable<U>
function inline(fn: (x: any) => any, src: any): any {
return null
}
const iarr = inline (fn, [1, 2]) // <-- string[]
const iset = inline (fn, new Set([1, 2])) // <-- Iterable<string>
Everything is in place and iarr
and iset
are correctly typed.
Now let's curry this function:
function pointfree <T, U>(fn: (x: T) => U): (src: T[]) => U[]
function pointfree <T, U>(fn: (x: T) => U): (src: Iterable<T>) => Iterable<U>
function pointfree(fn: (x: any) => any) {
return (src: any): any => null
}
const curried = pointfree (fn) // <-- (src: number[]) => string[]
const parr = curried ([1, 2]) // <-- string[]
const pset = curried (new Set ([1, 2]))
// ^^^^^^^^^^^^^^^
// Argument of type 'Set<number>' is not assignable to parameter of
// type 'number[]'.
// or without caching same error
const pset = pointfree (fn) (new Set([1, 2]))
pset
is evaluated as soon as pointfree (fn)
is reached and is auto casted to the first type of the overloads which is an array of numbers, even without caching as curried
It's very common in fp to need to partially apply function... Thanks in advance
Upvotes: 0
Views: 209
Reputation: 330571
Overloads are useful when different parameter types should result in different return types. It's not useful to have two different overload signatures with the same parameter types. That's because, as the handbook says:
[The compiler] looks at the overload list, and proceeding with the first overload attempts to call the function with the provided parameters. If it finds a match, it picks this overload as the correct overload.
In your definition of pointfree
, the parameter lists are identical for each overload. That means the compiler will always choose the first one if it chooses one at all.
So, what's the right way to do this? I suggest having a single call signature that returns an overloaded function. Like this:
// call signature, returns overloaded function
function pointfree<T, U>(fn: (x: T) => U): {
(src: T[]): U[],
(src: Iterable<T>): Iterable<U>
};
// implementation signature is the same as before
function pointfree(fn: (x: any) => any) {
return (src: any): any => null
}
Let's use it:
const curried = pointfree(fn)
// const curried: {
// (src: number[]): string[];
// (src: Iterable<number>): Iterable<string>;
// }
Now you can see that curried
is an overloaded function with two call signatures. So the following calls behave as you expect:
const parr = curried([1, 2]) // <-- string[]
const pset = curried(new Set([1, 2])) // <-- Iterable<string>
Okay, hope that helps. Good luck!
Upvotes: 2