Array.prototype.mapNotNull

But no other language lets you pick the value. I'm prepared to be convinced, I just find it interesting that the pattern appears to have no precedent in any other mainstream language. So using a different name may help signify the difference. That said, I agree that someone searching for this is likely to use the terms map and filter.

I appreciate the time series use case, I can buy that.

I'm not saying it will never arise. I'm coming at this from an angle of how intuitive the code would be when someone reads it for the first time. How likely they will make the right guess of what the method does. The explicitness of the Kotlin name appeals to me in this regard, even if it means that some use cases would need to use .map and .filter or .flatMap. It may be that I am under estimating how intuitive the arbitrary ignored value pattern is and over valuing Kotlin's name.

1 Like

In most languages you can cheaply and idiomatically use an Option or something similar to achieve the same thing. We can’t, so we’ve got to work with what we have.

I don’t think it’s actually important to signify the difference - it’s accomplishing the same thing, in almost the same way, just with minor accommodations for language-level differences. The important thing is, what do you reach for when you have a problem of this shape? And the answer is that you reach for filterMap, no matter what language you’re using, and then the question of exactly how you use that thing will depend on details about the language. Or conversely, as a reader of code, seeing filterMap is on its own sufficient to tell you what the problem being solved is, and then you can look at the details of how it’s being used and how it works in this particular language if you need that level of resolution.

I think the optional parameter will be pretty niche, so people who are encountering it for the first time will probably have to look it up. I’m OK with that; in the absence of named parameters, that’s usually what happens. For the common case where you don’t need the optional parameter, I think it will be obvious enough.

That said I think in a lot of cases it will still be pretty clear even with the optional parameter:

let skip = {};
return list.filterMap(x => x.isValid ? x.value : skip, skip);

or in the case that the values are known to be non-negative numbers if not null you can do something like

return list.filterMap(x => x.isValid ? x.value : -1, -1);

or whatever. I think for many readers just seeing that this is filterMap would be enough to figure out what’s going on here.


Even leaving aside the question of whether to take the optional parameter, I really think we should go with the common name for this and not Kotlin’s bespoke name. There’s a lot of value in using the common names for things.

(… Again, if browsers are even willing to try shipping this, under any name.)

1 Like

I also strongly agree with the filterMap naming over mapNotNull, for different reasons. mapNotNull suggests it maps over the non-null values, but since map doesn’t change the length of the list, presumably those null values just stay in the result, unchanged. filterMap is very clear about what’s happening - there’s a filter and a map, and filter does remove items and change the length of a list. Very straightforward to understand.

2 Likes

JS is not alone in this. The other languages that don't have an option pattern chose to hard code which value(s) gets filtered out. Not saying we can't innovate, Im just triple checking we are improving on these earlier designs. It does sound like there are good reasons to do something slightly novel here.

Then it is the case that I was over valuing the clarity of that name and concede to filterMap being the better name.

@tabatkins raises a good point about filterMap having clearer intent. The other angle to consider is that mapNotNull is clearer in Kotlin because it does not have to deal with undefined. There is only null. In JS land, mapNotNull could be interpreted as:

  • Removes null only
  • Remove null and undefined
  • Removes null, undefined, and empty string
  • Removes all falsy values

Not saying these are correct, but common ways people could interpret this in the JS world.

Following your train of thought, @aclaymore, that filtering out null is the most common pattern, I agree, hence this started out as mapNotNull. However, after the discussion, we are moving towards the name filterMap. I find it inconsistent that filterMapwould only take a map function and filter out null only. The name filterMap strongly implies configurable filtering behavior, like its namesake filter

I think if you go with filterMap, it makes far more sense to pass the value in. @bakkot said people can look it up when they encounter it. While developers can look up unfamiliar methods, I wonder if, by defaulting to null, we make this more likely by not having the value parameter.

I’ll note that iterators also provide a single pass without intermediate array, clear declarative intent, and better performance characteristics, and they already exist in the language:

const emails = users.values()
  .map(user => user.email)
  .filter(email => email !== null)
  .toArray();

The problem with filterMap is that the name comprises two existing operations. filter takes a callback, map takes a callback, does filterMap take two callbacks? Does the filter run on the input, or the output? Filtering input would be useful in situations where the mapFn is expensive, but existing flatMap suggests the mapping’s done first, then the flattening/filtering. On the other hand, if filtering output, writing a callback seems unnecessarily verbose. Besides, both filter-then-map and map-then-filter use cases with two callbacks are already covered by iterators.

That leaves us with the use case of map first, then discard unwanted values. I think it’d be better to avoid having filter in the name. From prior art listed above compactMap(mapFn, [unwanted]) looks reasonable. And it should remove undefined by default, because that’s what a missing argument is.

1 Like

That's not a problem; flat and map both take callbacks, and flatMap also takes a callback.

Indeed, just like flatMap, filterMap would have to do the map first, and then the filter.

flat doesn’t. The problem I was alluding to is that filter already means filter-by-predicate in JS; and the operation that does map and then prune-by-value (i.e. something other than filter) shouldn’t be named filterMap.

oh lol true, good call

Valid points, however, I am not sure I like compactMap either. What does compact really mean when conjointed with map? To me, no idea. I would have to look this function up; however, there's nothing wrong with that.