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

Refine nothings #14

Open
mjgpy3 opened this issue Sep 14, 2023 · 3 comments
Open

Refine nothings #14

mjgpy3 opened this issue Sep 14, 2023 · 3 comments

Comments

@mjgpy3
Copy link
Contributor

mjgpy3 commented Sep 14, 2023

I don't really know if this is possible in TypeScript so I'm throwing it here to start discussion.

Background

In flow we tended to use ?Foo which ends up being Foo | null | undefined in TypeScript. Having two values for nothing (e.g. null and undefined) is unnecessary and yields a lot of unnecessary checks.

In many places we've started to remove | undefined in favor of exclusively using null to mean "no value".

Desired behavior

Combinators should not add extra types but should preserve the fidelity of the input types.

E.g. the following should compile

let a: number | null = getA()
let b: number | undefined = getB()
let c: number | null | undefined = getC()

const add1 = (v: number) => v+1;

a = mmap(add1, a)
b = mmap(add1, b)
c = mmap(add1, c)

Actual Behavior

Re-definitions of a and b won't compile because mmap returns number | null | undefined.

@stackptr
Copy link
Member

This seems to be possible when using function overloads in an example Playground:

declare function mmap<I extends any, O>(f: (v: I) => O, v?: I): O
declare function mmap<I extends any, O>(f: (v: I) => O, v?: I | null): O | null

@stackptr
Copy link
Member

It appears the existing mmap implementation can be overloaded:

function mmap<I extends any, O>(f: (v: I) => O, v?: I): O
function mmap<I extends any, O>(f: (v: I) => O, v?: I | null): O | null
function mmap<I extends any, O>(f: (v: I) => O, v?: I | undefined): O | undefined
function mmap<I extends any, O>(f: (v: I) => O, v?: I | null | undefined): O | null | undefined {
  return v === undefined ? undefined : v === null ? null : f(v)
}

@StevenXL
Copy link

StevenXL commented Nov 7, 2023

It appears the existing mmap implementation can be overloaded:

function mmap<I extends any, O>(f: (v: I) => O, v?: I): O
function mmap<I extends any, O>(f: (v: I) => O, v?: I | null): O | null
function mmap<I extends any, O>(f: (v: I) => O, v?: I | undefined): O | undefined
function mmap<I extends any, O>(f: (v: I) => O, v?: I | null | undefined): O | null | undefined {
  return v === undefined ? undefined : v === null ? null : f(v)
}

I don't think that overloading is the path I'd take here. Doesn't the code above mean that we are going to end up returning I | null | undefined for some I, which is the problem we want to get rid of? We are now able to call the function in more ways, but we still end up with the same return type of the implementation signature.

On a tangential point, but while we are here, why do we have v? instead of v? I don't think I'd like to call mmap with only a function from I to O?

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