Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Learn You a Haskell explains the unwords function.

$unwords ["hey","there","mate"]
"hey there mate"

Here's my implementation.

unwords' :: [String] -> String
unwords' [] = []
unwords' (x:xs)
  | null xs   = x
  | otherwise = x ++ " " ++ unwords' xs

Is there a possible implementation with fold? I tried the following, but the " " got appended to the end.

$foldr (\x acc -> x ++ " " ++ acc) [] ["hey", "there", "mate"]  
"hey there mate "

Also, how can I append a String([Char]) to another String without ++?

Lastly, please critique in general.

share|improve this question

3 Answers 3

up vote 10 down vote accepted

Your foldr solution doesn't work because it's using the empty list you're passing in the final step. You can use foldr1 instead, it uses the final element of the list in place of being passed an accumulator value.

Looking to the folds to implement unwords isn't a bad idea, but there are other high-level functions that you can use to write a more terse or readable version. Let's start from a verbal description of what unwords is doing.

Insert a space character between every String in a list, then join the resulting Strings together.

The latter half of that description is easy, we know that String is really [Char], and we can easily flatten doubly-nested lists with concat. The former portion we could implement on our own, or search Hoogle for to see if anything already exists in the Prelude or other modules that could help us out. In this case, we're looking for a function with the type a -> [a] -> [a], that is, we want to pass it a value and a list and have it return a list with that value inserted between each pair of elements. As luck would have it, there's a function in Data.List that does exactly what we're looking for called intersperse that comes up as the first result if we perform that search.

Using these two functions, we can write a very short version of unwords that reads almost like prose.

import Data.List (intersperse)
import Prelude hiding (unwords)

unwords :: [String] -> String
unwords = concat . intersperse " "

Besides the aesthetic appeal of this solution, to me this illustrates the power of thinking about what you want to do in Haskell, instead of thinking about how it's going to be done.

share|improve this answer
    
thanks for your thoughtful answer, bisserlis. Such a powerful 1 liner for unwords that you've provided. –  Kevin Meredith May 14 at 23:45

The otherwise case is redundant.

unwords' :: [String] -> String
unwords' [] = []
unwords' (x:xs) = x ++ " " ++ unwords' xs

To use a fold but not get the extra space, use foldr1:

unwords'' :: [String] -> String
unwords'' xs
  | null xs   = []
  | otherwise = foldr1 (\x acc -> x ++ ' ' : acc) xs
share|improve this answer
    
In the 2nd implementation, I observe your first guard: null xs = []. However, are you concerned that foldr1 can throw a run-time exception if that check was not there? Looking at code that depends on a sequence (example: getting a value from an Option in Scala) seems fitting for a monad, no? My concern with foldr1 -stackoverflow.com/q/23183935/409976. P.S. I've used monads a bit in Scala, but I'm still new to them. –  Kevin Meredith May 14 at 2:42
    
Yes, that's why the null xs guard is there. –  200_success May 14 at 2:54

Here the shortest version I could come up with:

unwords = tail . (>>= (' ' :))

Remember that >>= is just concatMap for lists. So this code prepends every String in the list with a space, concats it and than throws the very first character (the leading space) away using tail.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.