How to filter String to [Maybe Char]?

filter (/=' ') "ab c" == "abc"

Is there a way to do the same but produce [Just ‘a’, Just ‘b’, Just ‘c’] in one step?

I could do

map Just (filter (/=' ') "ab c") == [Just 'a',Just 'b',Just 'c']

And it would be fine for the small amount of data I’m working with but am curious to do in one step.

map Just . filter (/= ' ') $ "xy z"

is readable and plenty fine. I don’t think there is a shorter way.

edit: missed a $

3 Likes

We could use Applicative Functors.

First we use pure to create an Applicative Functor, and then use <*> to apply it to Just "ab c".

pure (filter (/=' ')) <*> Just "ab c"     -- gives "abc"

I don’t think that your current version works. It needs parentheses around the function otherwise the function composition is applied in the wrong order. Also, map breaks up the string into characters.

This version works, it :

Just . filter (/= ' ')  $ "xy z"
1 Like

I’m pretty sure that’s what the OP wants:

The only issue with what f-a wrote is that it needs a $.

1 Like

If I’m understanding correctly, seems my 2 step method

map Just (filter (/=' ') "ab c")
-- [Just 'a',Just 'b',Just 'c']

is as good as it gets?

You could probably do this, though I wouldn’t say it’s necessarily better or simpler. It is clean though :slight_smile:

[Just c | c <- “ab c”, c /= ‘ ‘]
3 Likes

Or alternatively using do notation (with the list monad) which is equivalent to the list comprehension:

justLetters :: [Maybe Char]
justLetters = do
  c <- “ab c”
  guard (c /= ‘ ‘)
  pure (Just c)
6 Likes

Thank you for pointing that out.

do is using Monad. Does c <- “ab c” also mean a Monad is being used?

1 Like

Yes, specifically the list monad. The keyword to look up is “list comprehensions”, which are similar to linq from C#. Really list comprehensions are just the list monad with some bells and whistles

. o 0 (what’s a monad?)

From the original question:

This seems to imply that the method above would be inefficient for large amounts of data, but you don’t say why you think so.

Are you imagining that it will compile to code that loops over the string twice, once for filter and then again for map? If so, you probably don’t have to worry about that, because the optimisation called “stream fusion” will be applied and the map and filter will be combined into a single loop. (See the “Fusion” section of this wiki page for a description.)

2 Likes

Small correction: base still uses short cut fusion a.k.a. foldr/build fusion, because sometimes GHC still isn’t able to optimize stream fusion properly.

1 Like

Had use list comprehensions but didn’t realize Monad was involved.

It isn’t.

The list type being monadic is a Haskell artefact - Lazy ML and Miranda(R) also supports list comprehensions, but have no type classes whatsoever.

1 Like

You could also do this, not that it would be very efficient:

concatMap (\c -> if c == ' ' then [] else [Just c]) "ab c"
1 Like