November 18, 2018

Ramda: propOr vs prop + defaultTo are not the same

I thought these two lines would be equivalent, but they aren’t

propOr("Canada", "country", address);
pipe(prop("country"), defaultTo("Canada"))(address);

The goal of these lines to look up a country property on an address object, and provide a fallback value of Canada if there isn’t something useful there. What’s the difference? That depends how you define something useful.

defaultTo counts null, undefined, and NaN as not useful values. propOr will considers any value as useful, as long as the requested property exists.

That means if address looks like this:

const address = {
  addressLine1: "18 Ork St",
  addressLine2: "Suite 105",
  postalCode: "L4L4L4",
  province: "ON",
};

both propOr and the pipe-based function return Canada

propOr("Canada", "country", address) == pipe(prop("country"), defaultTo("Canada"))(address);

// returns `true`

but, if address looks like this (notice country: null here)

const address = {
  addressLine1: "18 Ork St",
  addressLine2: "Suite 105",
  postalCode: "L4L4L4",
  province: "ON",
  country: null,
};

then

propOr("Canada", "country", address) == pipe(prop("country"), defaultTo("Canada"))(address);

// returns `false`

Why?

propOr sees the country property is set, and passes its value along to us, even if it is set to null (or undefined, or NaN).

The combined pipe function’s defaultTo will replace undefined, null, or NaN with the default value, so null value is replaced with the default value.

So which one do I use?

It depends. If your system accepts values of null or undefined as meaningful, use propOr. If you need to guard your system against null or undefined, use the piped version.