Related to this code golf challenge, I tried to find acronyms with Haskell without using regular expressions.

The idea is to split the input string at every space or dash before finally gluing the heads of these parts together, if they are uppercase.

This is my code:

import System.Environment
import Data.Char

main :: IO ()
main = do
[inp] <- getArgs -- get input from the command line
putStrLn $getAcronym inp getAcronym :: String -> String getAcronym [] = [] getAcronym s = foldr step [] parts where parts = split isWordSep s -- split into words step x acc = if isUpper . head$ x
then head x : acc else acc -- glue uppercase heads together

split :: (a -> Bool) -> [a] -> [[a]]
split p []       = []
split p s@(x:xs)
| p x       = split p xs -- discard trailing white spaces
| otherwise = w : split p r -- continue with the rest
where (w, r) = break p s -- seperate prefix

isWordSep :: Char -> Bool
isWordSep x = x == ' ' || x == '-'


As this really seems like a very simple problem, my code looks like way too much complexity.

Do you have any helpful improvements to slim down my code?

• getAcronym = filter isUpper . map head . wordsBy isWordSep, using Data.List.Split's wordsBy. Jul 23 '17 at 23:26
• That's a nice idea. I didn't know wordsBy. Jul 24 '17 at 5:01
• @Erich keep in mind that wordsBy is from the package split. I'm not sure whether that is allowed on PCG.
– Zeta
Jul 24 '17 at 12:22
• @Zeta Me neither, but it doesn't really matter, as it was just for practicing purposes. On PCG I would use Regular Expressions anyway. Jul 24 '17 at 12:29

Since you asked for help slimming down your code, I'll give you some hints that might otherwise be inadvisable.

1. Don't use do-notation. In fact, you can replace

main :: IO ()
main = do
[inp] <- getArgs -- get input from the command line
putStrLn $getAcronym inp  with main :: IO () main = putStrLn =<< (fmap (getAcronym . head) getArgs)  2. I'd replace isWordSep to take advantage of pattern matching. As an example: isWordSep :: Char -> Bool isWordSep ' ' = True isWordSep '-' = True isWordSep _ = False  3. Consider using a parser library. Monadic parser combinators are quite nice, and they accomplish what you'd probably use regular expression for, but with far nicer error messages and type safety. Here's one example using megaparsec: import Control.Monad import Data.Void import Text.Megaparsec import Text.Megaparsec.Char type Parser = Parsec Void String uppercaseWord :: Parser Char uppercaseWord = do h <- upperChar many$ noneOf "- "
pure h

acronymP :: Parser String
acronymP = some $do w <- uppercaseWord void (oneOf " -") <|> eof pure w acronym :: String -> IO () acronym = parseTest acronymP  You can unpack the do-notation to get: acronym :: String -> IO () acronym = parseTest (acronymP :: Parser String) where acronymP = some (uppercaseWord >>= ((void (oneOf " -") <|> eof) >>) . pure) uppercaseWord = upperChar >>= ((many (noneOf "- ") >>) . pure)  which, surprisingly enough, does indeed work. With the help of Gurkenglas, I have found a good solution for this problem: First, the getAcronym function can be dramatically reduced by using higher order functions and function composition: getAcronym :: String -> String getAcronym = filter isUpper . map head . split isWordSep  Second, the split function can be replaced with Data.List.Split's wordsBy function, reducing the whole code to the following: import System.Environment import Data.Char import Data.List.Split (wordsBy) main :: IO () main = do [inp] <- getArgs -- get input from the command line putStrLn$ getAcronym inp

getAcronym :: String -> String
getAcronym = filter isUpper . map head . wordsBy isWordSep

isWordSep :: Char -> Bool
isWordSep x = x == ' ' || x == '-'