MoeSattler
MoeSattler

Reputation: 6894

Typing a function that returns undefined if parameter is undefined

Let's say I have this function (try online):

type Person = { name: string }
function getName(p: ?Person): ?string {
  if (!p) {
    return p
  } else {
    return p.name
  }
}

const frank: Person = { name: 'Frank' }
/*
 * In order to work now, this has to have a maybe type
 * const name: ?string = getName(frank)
 * Even though I know it will is guaranteed to be a string
 */
const name: string = getName(frank)

I get an error, because Flow thinks that getName could return undefined. Though this happens only, if p would be undefined, which clearly is not the case.

Can I somehow type the function in a way, that the return type is undefined ONLY when the parameter is undefined and otherwise a string?


Edit:

Clarifying Example: I have an easy function:

function getName(person) {
  if (! person) {
    return undefined
  } else {
    return person.name
  }
}

const maybeFrank: ?Person = getFrank()
const definitelyMaria: Person = getMaria()

const frankName: ?string = getName(maybeFrank)
const mariaName: string = getName(definitelyMaria)

I would like to type it in a way, so:

Is that possible?

Upvotes: 2

Views: 1605

Answers (2)

Andrew Smith
Andrew Smith

Reputation: 1444

I liked the solution by @loganfsmyth, but found it to involve too much typing for each selector.

I came up with a way to make this more generic, which saves you from creating multiple type signatures for each selector.

type FinderT<A, B, C> = (A, B) => C
declare function maybe<B, C>(cb: FinderT<number, B, C>, id: number, other: B): C
declare function maybe<B, C>(cb: FinderT<number, B, C>, id: ?number, other: B): void // eslint-disable-line no-redeclare
export function maybe(cb, id, other) { // eslint-disable-line no-redeclare
  if (!id) {
    return undefined
  } else {
    return cb(id, other)
  }
}

Usage:

maybe(findPerson, 42, state)

This correctly returns a ?Thing or Thing based on whether the id is a number or a ?number. Currently it only handles one extra argument, but this could probably be expanded to include more arguments.

Upvotes: 0

loganfsmyth
loganfsmyth

Reputation: 161457

My opinion would be to remove the ? from the function argument and leave the disambiguation up to the call site. e.g.

type Person = { name: string }

function getName(person: Person): string {
  return person.name
}

const maybeFrank: ?Person = Math.random() > 0.5 ? {name: 'Frank'} : undefined;
const definitelyMaria: Person = {name: 'Maria'};

const frankName: ?string = maybeFrank && getName(maybeFrank)
const mariaName: string = getName(definitelyMaria);

otherwise, if you really want to, you can explicitly redeclare the function with explicit types

type Person = { name: string }

declare function getName(p: Person): string;
declare function getName(p: void): void;
function getName(p: ?Person): ?string {
  if (!p) {
    return undefined
  } else {
    return p.name
  }
}

const frank: Person = { name: 'Frank' }
const name: string = getName(frank)

Upvotes: 3

Related Questions