haskellhaskell-lens

Increment suffix with Lens


How to write a lens-based function incrementing numerical suffix (if it exists) and keeping the string as is if no such one?

"aaa1" -> "aaa2"
"aaa_123" -> "aaa_124"
"" -> ""
"aaa" -> "aaa"
"123a" -> "123a"
"3a5" -> "3a6"

I have feeling that I need some prism maybe, first, but no idea which one (suffixed does not get a function as an argument but a scalar).


Solution

  • One quick way to figure out which kind of optic you need is considering the properties it should have. In your case, we can ask the questions suggested by this answer of mine, and note that:

    The lack of reversibility suggests you want a traversal, rather than a prism. With the lens library, implementing a traversal is a matter of writing a function which fits the Traversal type:

    type Traversal s t a b = forall f. Applicative f => (a -> f b) -> s -> f t
    

    Here is one way of doing it for your purposes:

    import Control.Lens
    import Text.Read (readMaybe)
    import Data.Char (isDigit)
    import Data.List.Extra (spanEnd)
    
    -- numericSuffix :: Applicative f => (Integer -> f Integer) -> String -> f String
    numericSuffix :: Traversal' String Integer
    numericSuffix f str =
        (prefix ++) <$> maybe (pure "") (fmap show . f) (readMaybe suffix)
        where
        (prefix, suffix) = spanEnd isDigit str
    

    Notes on the implementation:

    Incrementing the suffix can then be straightforwardly done through over:

    incrementSuffix :: String -> String
    incrementSuffix = over numericSuffix succ
    
    ghci> incrementSuffix "aaa1"
    "aaa2"
    ghci> incrementSuffix "aaa_123"
    "aaa_124"
    ghci> incrementSuffix ""
    ""
    ghci> incrementSuffix "aaa"
    "aaa"
    ghci> incrementSuffix "123a"
    "123a"
    ghci> incrementSuffix "3a5"
    "3a6"