Here is the corresponding Playground.
Here is a function which I cannot change at all:
function cannotChangeThisFunc<A>(v: (A | null) | ((u: (A | null)) => (A | null))) {
// cannot change this body
}
Now I created a function which literally has the same signature as cannotChangeThisFunc
but disallows all null
values:
function cannotChangeThisSignature<A>(v: A | ((u: A) => A)) {
cannotChangeThisFunc<A>(/* ... */);
}
The goal is that a call to cannotChangeThisSignature
should invoke cannotChangeThisFunc
with the same value. The following does not work:
function cannotChangeThisSignature<A>(v: A | ((u: A) => A)) {
// can change this body
cannotChangeThisFunc<A>(v);
}
Neither does this (besides the fact that would be runtime checking and should be compile time checking):
function cannotChangeThisSignature<A>(v: A | ((u: A) => A)) {
// can change this body
if (typeof v === "function") {
cannotChangeThisFunc<A>((vv: (A | null)) => (v(vv)));
} else {
cannotChangeThisFunc<A>(v);
}
}
Can someone elaborate please?
Here is the corresponding Playground.
CodePudding user response:
There are two problems:
- A function given to the one with
null
(cannotChangeThisFunc
) has to be able to deal withnull
inputs, but the function of the non-null version (cannotChangeThisSignature
) cannot do that. - Type
A
can be a function itself, so(typeof v === "function")
does not guarantee that it is a function of typeA => A
The first one you can handle by wrapping the function. This is fine, since cannotChangeThisSignature
does not receive null
input. However, the second one I don't think you can solve except telling typescript that v
must be A => A
. So basically cheat a bit:
type Fun<A> = ((u: A) => A) // just for readability
function cannotChangeThisSignature<A>(v: A | Fun<A>) {
if (typeof v === "function"){
const wrappedV = wrapWithNullHandler(v as Fun<A>) // read v as A => A
cannotChangeThisFunc<A>(wrappedV);
} else {
cannotChangeThisFunc<A>(v);
}
}
function wrapWithNullHandler<A>(fun: Fun<A>): Fun<A|null> {
return (aOrNull: (A|null)) => (aOrNull === null) ? null : fun(aOrNull)
}
That means that you could do
const f = () => 42
cannotChangeThisSignature(f) // no ts error but not right either
and you might get an exception. I don't think that is a limitation in typescript, but a language problem, as it is not possible to discern at runtime if a given function is A => A
or something else. That means that there is no clean way to this at all.
But when cheating is the only solution anyway, and handling the null
s isn't really necessary, I would just skip that step:
function cannotChangeThisSignature<A>(v: A | Fun<A>) {
if (typeof v === "function"){
cannotChangeThisFunc<A>(v as Fun<A|null>); // it' ok, ts
} else {
cannotChangeThisFunc<A>(v);
}
}