Like DarkOtter suggested, Edward Kmett's lens
library has you covered, but Lens
is too weak and Iso
is slightly too strong since unwords . words
isn't an identity. You could try a Prism
instead.
wordPrism :: Prism' String [String]
wordPrism = prism' unwords $ \s ->
-- inefficient, but poignant
if s == (unwords . words) s then Just (words s) else Nothing
Now you can define capitalize
as
capitalize' :: String -> String
capitalize' = wordPrism %~ map capWord
-- a.k.a = over wordPrism (map capWord)
but this has fairly pathological default behavior for your case. For String
s which can't be mapped as isomorphisms (strings with multiple spaces in a row inside of them) over wordPrism g == id
. There ought to be an "over if possible" operator for Prism
s, but I don't know of one. You could define it though:
overIfPossible :: Prism s t a b -> (a -> b) -> (s -> Maybe t)
overIfPossible p f s = if (isn't p s) then Nothing else Just (over p f s)
capitalize :: String -> Maybe String
capitalize = wordPrism `overIfPossible` map capWord
Now, really, both of these are pretty unsatisfactory since what you really want is to capitalize all words and retain the spacing. For this (words, unwords)
is too weak generally due to the non-existence of isomorphism that I've highlighted above. You'd have to write your own custom machinery which maintains spaces after which you'd have an Iso
and could use DarkOtter's answer directly.