Errors
The code block above is an example where your program will fail. Any empty line will lead to an empty list in words ""
and therefore to head []
, which calls error
.
Standard library
That being said, there are some other issues with your code. You use unlines
in fixLines
, yet leadWords
is essentially lines
, although it also uses head . words
internally. If we rewrite leadWords
with map
, we end up with
leadWords = map (head . words) . lines
which immediately shows the possible error on empty lines. Still, we can fix this if we write another function:
firstWord :: String -> Maybe String
firstWord xs = case words xs of
(x : _) -> Just x
_ -> Nothing
Now we can use mapMaybe
from Data.Maybe
to write leadWords
:
leadWords :: String -> [String]
leadWords = mapMaybe firstWord . lines
Type signatures
Other than that, you should really add type signatures to your top-level functions. Only fixLines
has one, and its not clear why that one got special handling.
Code complexity
main
is overly complicated, too. We can just inline mainWidth
, as its local binding doesn't yield any more comfort:
main :: IO ()
main = do
args <- getArgs
case args of
[input, output] -> interactWith fixLines input output
_ -> putStrLn "error: exactly two arguments needed"
Alternatively, we can provide mainWith
as top level function and use that in main
.
Complete code
If we apply all remarks given above, we end up with something similar to the following code:
import Data.Maybe (mapMaybe)
mainWith :: (String -> String) -> IO ()
mainWith f = do
args <- getArgs
case args of
[input, output] -> interactWith f input output
_ -> putStrLn "error: exactly two arguments needed"
fixLines :: String -> String
fixLines = unlines . leadWords
main :: IO ()
main = mainWith fixLines
-- for completeness
leadWords :: String -> [String]
leadWords = mapMaybe firstWord . lines
firstWord :: String -> Maybe String
firstWord xs = case words xs of
(x : _) -> Just x
_ -> Nothing
-- That's just personal preference, your style is fine.
interactWith :: (String -> String) -> FilePath -> FilePath -> IO ()
interactWith f input output = readFile input >>= writeFile output . f
In short:
- add type signatures to top-level functions
- don't use
head
on lists that might be empty
- use standard library types when possible (
Maybe
)
- use standard library functions when possible (
map
, lines
and mapMaybe
)