简体   繁体   中英

Haskell: concat a list of tuples with an element and a list: [(a,[b])] -> [(a,b)]

I want to concat in a list of tuples of elements and Strings, each char with the element.

For example: [(True, "xy"), (False, "abc")] 􏰁-> [(True,'x'),(True,'y'),(False,'a'), (False,'b'),(False,'c')]

I do have a solution, but im wondering if there is a better one:

concatsplit :: [(a,[b])] -> [(a,b)]
concatsplit a = concatMap (\(x,y)-> concatsplit' (x,y)) a

concatsplit' :: (a,[b]) -> [(a,b)]
concatsplit' y = map (\x -> ((fst y),x)) (snd y)

Why not a simple list comprehension?

List comprehensions can often do as much as higher order functions, and I think that if you don't need to change the data but only "unpack" it, they are pretty clear. Here's a working example:

concS :: [(a,[b])] -> [(a,b)]
concS ls = [(a,b) | (a,x) <- ls, b <- x]

sequenceA makes it really easy:

concatsplit = concatMap sequenceA

Or generalize it even further:

concatsplit = (>>= sequenceA)

Details:

  • sequenceA has type (Applicative f, Traversable t) => t (fa) -> f (ta) . This means that if you have a type with a Traversable on the "outside" and an Applicative on the "inside", you can call sequenceA on it to turn it inside-out, so that the Applicative is on the "outside" and the Traversable is on the "inside".
  • In this case, (True, "xy") has type (Bool, [Char]) , which desugars to (,) Bool ([] Char) . There's an instance of Traversable for ((,) a) , and there's an instance of Applicative for [] . Thus, you can call sequenceA on it, and the result will be of type [] ((,) Bool Char) , or [(Bool, Char)] with sugar.
  • As it turns out, not only does sequenceA have a useful type, but it does exactly the thing you need here (and turns out to be exactly equivalent to concatsplit' ).
  • concatMap has type Foldable t => (a -> [b]) -> ta -> [b] . (>>=) has type Monad m => ma -> (a -> mb) -> mb . When specialized to lists, these become the same, except with their arguments in the opposite order.

The other answers show how to do this idiomatically from scratch, which I like a lot. It might also be interesting to show how you might polish what you've already got. Here it is again as a reminder:

concatsplit a = concatMap (\(x,y)-> concatsplit' (x,y)) a
concatsplit' y = map (\x -> ((fst y),x)) (snd y)

The first thing I'd consistently change is called "eta reduction", and it is when you turn something of the shape \\x -> foo x into just foo . We can do this in the argument to concatMap to get

concatsplit a = concatMap concatsplit' a

and then again in the argument to concatsplit to get:

concatsplit = concatMap concatsplit'

Looking at concatsplit' , the thing I like least is the use of fst and snd instead of pattern matching. With pattern matching, it looks like this:

concatsplit' (a,bs) = map (\x -> (a,x)) bs

If you really wanted to practice your eta reduction, you might notice that (,) can be applied prefix and change this to

concatsplit' (a,bs) = map (\x -> (,) a x) bs
                    = map ((,) a) bs

but I think I'm just as happy one way or the other. At this point, this definition is small enough that I'd be tempted to inline it into concatsplit itself, getting:

concatsplit = concatMap (\(a,bs) -> map ((,) a) bs)

This looks like a pretty good definition to me, and I'd stop there.

You might be bugged by the almost-eta-reduction here: it's almost the right shape to drop the bs . An advanced user might go on, noticing that:

uncurry (\a bs -> map ((,) a) bs) = \(a,bs) -> map ((,) a) bs

Hence with some eta reduction and other point-free techniques, we could transition this way:

concatsplit = concatMap (uncurry (\a bs -> map ((,) a) bs))
            = concatMap (uncurry (\a -> map ((,) a)))
            = concatMap (uncurry (map . (,)))

But, personally, I find this less readable than where I declared I would stop above, namely:

concatsplit = concatMap (\(a,bs) -> map ((,) a) bs)

You can also use the monad instance for lists:

concatSplit l = l >>= \(a,x) -> x >>= \b -> return (a,b)

Which can be simplified to:

concatSplit l = l >>= \(a,x) -> map ((,) a) x

And reformatted to do notation

concatSplit l = do
  (a,x) <- l
  map ((,) a) x

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM