Revisiting "List.filter"

347 views
Skip to first unread message

Evan Czaplicki

unread,
Jan 16, 2015, 8:32:23 AM1/16/15
to elm-d...@googlegroups.com
We had a thread a while ago about renaming filter. Today I was doing a class for folks new to Elm and had some confusion on this line. I'll reproduce a simplified form here:

List.filter (\obj -> obj.id /= id) objects

The goal is to get rid of one element. Specifically, the element with an id of id. This is crazy. Here are two variations with Janis's suggestion:

List.keepIf (\obj -> obj.id /= id) objects
List.dropIf (\obj -> obj.id == id) objects

Both are waaaay clearer. It's pretty stark I think. So I got to thinking about what filterMap should be called, and the only viable alternative I can think of is as follows:

List.maybeMap : (a -> Maybe b) -> List a -> List b

I'll ask around among novices to see if how they feel. I'm pretty on board with the keepIf/dropIf changes at this point though.

Joey Eremondi

unread,
Jan 16, 2015, 8:47:15 AM1/16/15
to elm-d...@googlegroups.com
Those names seem good to me. I'm often wanting a "dropIf" function and rolling my own is always just a bothersome line of boilerplate.

Jeff Smits

unread,
Jan 16, 2015, 9:01:45 AM1/16/15
to elm-discuss
From the name alone I'd expect List.maybeMap to have type (a -> Maybe a) -> List a -> List and the property List.length l == List.length (List.maybeMap f l).
I'm very much for banning the name filter, because there is always a "direction" question.* But that doesn't mean you need to ban filter from being part of a name! filterMap is a good descriptive name. When you see it used the Maybe is likely easily visible in the function; or the function name together with "some kind of filtering map?" will likely be enough to figure out what it does. IMHO maybeMap will only confuse more!

* I dislike sort for the same reason. I can never remember if it sorts descending or ascending, and even then I wouldn't trust on there being a standard that every programming language conforms to, so remembering for each programming language I use is inefficient.

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Rehno Lindeque

unread,
Jan 16, 2015, 9:56:52 AM1/16/15
to elm-d...@googlegroups.com
This would probably drive other fp people mad (sorry!), but with the name being freed up, even this?

List.filter : List (Maybe a) -> List a
List.filterMap : (a -> Maybe a) -> List a -> List a

For some reason it this resonates with my mental image of filter being some kind of sieve (e.g. say you pour maybes through your filter - Nothing is like sand and Justs are like little pebbles :-)). Admittedly I might get a bit confused jumping between Haskell and Elm for this one though.

Evan Czaplicki

unread,
Jan 16, 2015, 10:09:40 AM1/16/15
to elm-d...@googlegroups.com
Ooooh, that's cool Rehno! I really like the correspondence with the concat pair:

concat : List (List a) -> List a
concatMap : (a -> List b) -> List a -> List b

filter : List (Maybe a) -> List a
filterMap : (a -> Maybe b) -> List a -> List b

Very cool :) Laszlo says there should at least be a one release delay between deprecating existing filter and introducing your version. Right now I feel weird about the change, but perhaps in the future it'll be more obvious. Luckily that matches with Laszlo's plan anyway!

!!!
Evan




Rehno Lindeque

unread,
Jan 16, 2015, 10:14:55 AM1/16/15
to elm-d...@googlegroups.com
Sweet! I should probably describe Nothing as being insubstantial like air water and Justs being pebbles for an even better mental image :)

Rehno Lindeque

unread,
Jan 16, 2015, 10:42:05 AM1/16/15
to elm-d...@googlegroups.com
Another possible option, if you'd prefer to avoid clashing with existing languages is something like lodash's compact.

concat : List (List a) -> List a
concatMap : (a -> List b) -> List a -> List b

compact : List (Maybe a) -> List a
compactMap : (a -> Maybe b) -> List a -> List b

Jeff Smits

unread,
Jan 16, 2015, 10:58:53 AM1/16/15
to elm-discuss
compactMap isn't as clear a name filterMap IMO.
My first reaction to filter becoming the old justs function is that it's not a good idea. But I'm not sure.. Since it would take deprecation and another version to introduce this new thing anyway, I'll refrain from being the naysayer ;)

jonatha...@gmail.com

unread,
Jan 16, 2015, 12:20:22 PM1/16/15
to elm-d...@googlegroups.com
As an outsider, compact would be the most familiar and understandable to me (from Ruby, among others).  compactMap would be less so, but I could easily guess what mapCompact does.

Also, to the original question, keepIf and dropIf are definitely superior names.  For parity, you might consider renaming take to keep.

Sean Corfield

unread,
Jan 16, 2015, 12:37:45 PM1/16/15
to elm-d...@googlegroups.com
I agree that filter is very neutral and doesn’t make it clear what’s going on, so I’d be in favor of keepIf and dropIf.

Purely as a comparison here’s what Clojure has - note that keep is very different to filter (and I think is even more confusing):

user=> (doc filter)
-------------------------
clojure.core/filter
([pred coll])
  Returns a lazy sequence of the items in coll for which
  (pred item) returns true. pred must be free of side-effects.
nil
user=> (doc keep)
-------------------------
clojure.core/keep
([f coll])
  Returns a lazy sequence of the non-nil results of (f item). Note,
  this means false return values will be included.  f must be free of
  side-effects.
nil
user=> (doc remove)
-------------------------
clojure.core/remove
([pred coll])
  Returns a lazy sequence of the items in coll for which
  (pred item) returns false. pred must be free of side-effects.
nil

Hassan Hayat

unread,
Jan 16, 2015, 1:41:40 PM1/16/15
to elm-d...@googlegroups.com
+1 on this idea. I love it. I admit having to google this once in a while cuz my brain goes, "wait, does filter keep the stuff that matches the predicate or does it drop it?". 

+1 also for the idea of filter to be:

filter : List (Maybe a) -> List a

This will make code read nicely, especially when you have loads of functions from a -> Maybe b and want only the values who in some sense have succeded or passed the computation.

Rehno Lindeque

unread,
Jan 16, 2015, 1:47:45 PM1/16/15
to elm-d...@googlegroups.com
As an outsider, compact would be the most familiar and understandable to me (from Ruby, among others).  compactMap would be less so, but I could easily guess what mapCompact does.

@Jonathan on way to gain intuition for concatMap / compactMap would be think in terms of function composition: I.e. if you glue two functions together what do you get?

concatMap = concat << map     -- i.o.w. concatMap f = concat (map f)
compactMap = compact << map   -- i.o.w. compactMap f = compact (map f)

Of course this doesn't address your point that this might not be all that familiar to someone with Ruby a ruby background unfortunately (but maybe adding a code snippet like this to the docs everywhere it occurs will help to carry over one of the most common mental trick that Haskellers use - thinking in terms of function composition rather than function application)

Rehno Lindeque

unread,
Jan 16, 2015, 1:53:49 PM1/16/15
to elm-d...@googlegroups.com
concatMap = concat << map     -- i.o.w. concatMap f = concat (map f)
compactMap = compact << map   -- i.o.w. compactMap f = compact (map f)

Oops like this sorry, 

concatMap f = concat << map f    -- i.o.w. concatMap f xs = concat (map f xs)
compactMap f = compact << map f  -- i.o.w. compactMap f xs = compact (map f xs)

I'm being strangely dyslexic today

Hassan Hayat

unread,
Jan 16, 2015, 2:07:04 PM1/16/15
to elm-d...@googlegroups.com
Reading all this about concatMap made me wonder: why not also change concat and concatMap to flatten and flatMap respectively?

I mean, in my mind, concat means concatenate which is what the ++ operator does. In the dictionary, concatenate means, "link (things) together in a chain or series." I don't know what flattening a list of list has to do with linking a chain of things. Sure, that may be how you implement flatten (by calling ++ iteratively on every consecutive pairs of sublists) but that seems like an implementation detail.

Furthermore, in the documentation for concatMap, it says "Map a given function onto a list and flatten the resulting lists." 
I think it kinda indicates that "flatten" may be the natural way of talking about this process. As in, "I have a list of lists and I want to flatten it into one big list." As if you had a list of lists laying on a table and you apply a rolling pin on the list of lists until the outer square bracket breaks and you get one big flat list.

Max Goldstein

unread,
Jan 16, 2015, 5:32:58 PM1/16/15
to elm-d...@googlegroups.com
I think these are all great ideas. Recap
  • filter -> keepIf, dropIf
  • take -> keep
  • filter and filterMap for Maybes
  • concat(Map) -> flat(ten|Map)
I'm familiar with compact from Ruby but I think filter is better for that. And flatten seems more functional than concat.

I also wouldn't worry about the missing ten in flatMap relative to flatten. Flatten implies doing something. FlattenMap implies doing two things. FlatMap implies "You're mapping, but keeping it flat" -- the sort of atomic operation that functional languages use instead of mutation and for-loops.

As for semvar and stuff, you can add/alias everything but the new filter (and filterMap), and console.warn in the old versions.

Richard Feldman

unread,
Jan 16, 2015, 5:52:33 PM1/16/15
to elm-d...@googlegroups.com
In general I dig the direction of this thread.

Some specific points of support:
  1. I agree that keepIf and dropIf are better than filter and reject (the traditional, less clear names I've seen for these).
  2. I don't have strong feelings about keep and take.
  3. If that change happens, I am definitely against repurposing the name filter for a function that does something else. Whenever in my programming life I've encountered a function with a well-established name that turned out to do something not-well-established, it's been a very frustrating experience. Novices in particular may get confused if they ask a friend "how does filter work?" and the friend gives them an answer for how filter works in most languages rather than the unusual way it works in Elm. I'd say just introduce keepIf and remove filter entirely.
  4. I also agree that compact is the term I expect for "get rid of all the Nothings in this list." Not only is that what this exact function is called in Ruby, it's also what this exact function is called in Underscore. Considering how popular Underscore is among JavaScript programmers in particular, that seems like an even stronger argument for choosing it.
  5. Given that, it seems like compactMap is the obvious name for what was originally proposed as maybeMap. It's unfortunate that compactMap looks so much like concatMap. Probably having those coexist would be suboptimal.
  6. I understand what "flatten" means fairly intuitively: List (List a) -> List a. You're "flattening" the list. I also know what "concat" means. However, I've always found "flatMap" more intuitive than "concatMap," because it feels more like what I'm doing is mapping and then flattening the result.
Put together, I'm in favor of:
  • filter, reject -> keepIf, dropIf
  • compact and compactMap for Maybes
  • concatMap -> flatMap

Hassan Hayat

unread,
Jan 16, 2015, 6:32:05 PM1/16/15
to elm-d...@googlegroups.com
@Richard, I kinda agree with you about filter. While filtering maybes is a much better meaning, imho, than that in other languages, it might be confusing, especially for people coming from other languages. 

So, how about we instead add the following functions to the standard library: 

isJust : Maybe a -> Bool
isNothing
: Maybe a -> Bool
isOk
: Result error value -> Bool
isErr
: Result error value -> Bool

This way, we can just say

keepIf isJust [Just 2,Just 4,Nothing, Just 42] --[2,4,42]

which is one word longer than filter but I think makes it super clear. It kinda reads like english, "keep if it is a just"

(sidenote: can we also change "Err" to "Error" in the Result library? It might feel weird to a newcome that we have cut out 2 letters from Error when we didn't shorten something like "Nothing" which could've been called "No" or "None")

Sean Corfield

unread,
Jan 16, 2015, 6:44:27 PM1/16/15
to elm-d...@googlegroups.com
Didn’t we recently remove something very similar to these?

David Sargeant

unread,
Jan 16, 2015, 8:28:40 PM1/16/15
to elm-d...@googlegroups.com
Definitely in favor of removing filter.  I never use it Clojure, since it's so confusing.  I always use remove instead.

Dobes Vandermeer

unread,
Jan 17, 2015, 2:32:53 AM1/17/15
to elm-d...@googlegroups.com

It may be a side note, but ideally flatten and flatMap will one day also flatten a Maybe as if it were a list.  I think this works in Scala already.  Then you don't need a special function for it.  I know this is a future item because currently there is no language facility to support this.  But it's something to keep in mind...

Whatever name is chosen may eventually be removed or become a synonym for flatten.

So putting flatten in the name might help.  Like if they're was a package called MaybeList it could be MaybeList.flatten.  Or List.flattenMaybes?

--

Aaron VonderHaar

unread,
Jan 17, 2015, 6:59:39 AM1/17/15
to elm-d...@googlegroups.com
How about

keepJusts : List (Maybe a) -> a
keepIfJust : (a -> Maybe b) -> List a -> List b

or `dropNothings` (not sure which of the two signatures that should be
for, though)
--
--Aaron V.
[ http://github.com/avh4 ]

Evan Czaplicki

unread,
Jan 17, 2015, 7:17:35 AM1/17/15
to elm-d...@googlegroups.com
As I work on the Promises stuff, I see more iterations of this _ and _Map pattern. For example, there could be these functions:

sequence : List (Promise x a) -> Promise x (List a)
sequenceMap : (a -> Promise x b) -> List a -> Promise x (List b)

interleave : List (Promise x a) -> Promise x (List a)
interleaveMap : (a -> Promise x b) -> List a -> Promise x (List b)

Where these functions are for running a bunch of promises in order or interleaved. Seems like _ and _Map is just a pattern that works in a lot of cases. I dislike flatMap partly because it breaks this pattern. Also, is it's weird that "compact" is not very verby to me.

petar...@googlemail.com

unread,
Jan 18, 2015, 4:34:31 PM1/18/15
to elm-d...@googlegroups.com
What about Smalltalk style names:

select: returns the elements of the receiver that satisfy a particular condition:

 
(2 to: 20) select: [:each | each isPrime] -→ #(2 3 5 7 11 13 17 19)  

reject: does the opposite:

 
(2 to: 20) reject: [:each | each isPrime] -→ #(4 6 8 9 10 12 14 15 16 18 20)

Max Goldstein

unread,
Jan 18, 2015, 9:59:58 PM1/18/15
to elm-d...@googlegroups.com
So that's where Ruby got those names.

There's a certain amount of subjectivity, but given that we have Signal.keepIf already, I think that's the way to go.

I think have take to get a prefix but keepIf is inconsistent.

I guess I could go for compact and compactMap, since filter seems to have many implied meanings.

Evan, I know you are (1) busy with promises (2) always reluctant to make hard changes, but I would go ahead and jump on this. You can add the new functions and deprecation warnings, and drop the old ones whenever core/2.0.0 comes out. Don't worry too much about flatMap not being flattenMap.

Rehno Lindeque

unread,
Jan 19, 2015, 5:15:28 AM1/19/15
to elm-d...@googlegroups.com
Evan, I know you are (1) busy with promises (2) always reluctant to make hard changes, but I would go ahead and jump on this. You can add the new functions and deprecation warnings, and drop the old ones whenever core/2.0.0 comes out. Don't worry too much about flatMap not being flattenMap.

I had been thinking of adding filter/filterMap or compact/compactMap to an elm-list-extra package for people that want to get ahead of core in the mean time. Just waiting for a free moment and to make sure consensus has been reached...

Alexey Zlobin

unread,
Jan 19, 2015, 5:53:31 AM1/19/15
to elm-d...@googlegroups.com
I don't really get an idea of (with any name including 'keepIfJust')
List.filterMap  : (a -> Maybe b) -> List a -> List b

Given 'keepJusts : List (Maybe b) -> List b' kind of function it could be written quite compactly and expressively in few ways.
List.filterMap f l = l |> map f |> keepJusts
List.filterMap f = map f >> keepJusts

And free readers from remembering one more function.

PS. Just a crazy idea (not even mine): what if Maybe become 'Nothing | Some a' instead of 'Nothing | Just a'?

Than we could have function names like
List.keepSome : List (Maybe b) -> List b
which would be both readable and name-consistent.


On Mon, Jan 19, 2015 at 1:15 PM, Rehno Lindeque <rehno.l...@gmail.com> wrote:
Evan, I know you are (1) busy with promises (2) always reluctant to make hard changes, but I would go ahead and jump on this. You can add the new functions and deprecation warnings, and drop the old ones whenever core/2.0.0 comes out. Don't worry too much about flatMap not being flattenMap.

I had been thinking of adding filter/filterMap or compact/compactMap to an elm-list-extra package for people that want to get ahead of core in the mean time. Just waiting for a free moment and to make sure consensus has been reached...

Reply all
Reply to author
Forward
0 new messages