RandomB
RandomB

Reputation: 3737

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).

Upvotes: 3

Views: 96

Answers (1)

duplode
duplode

Reputation: 34378

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:

  • Like a prism, your optic can fail, as there might not be a numeric suffix.

  • Unlike a prism, however, your optic is not reversible. suffixed is reversible because its target is the prefix, and the suffix being matched is fixed. In your case, the target is the suffix, and the prefix can't be known in advance.

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:

  • spanEnd comes from the extra package.

  • Round-tripping through Read and Show as done here will strip leading zeroes from the prefix. If that's undesirable (which seems rather likely), you'll need something more refined than show to format the modified suffix.

  • If the aforementioned round-tripping is not a problem, though, the implementation can be made very compact by using the _Show prism as a traversal:

    numericSuffix f str = (prefix ++) <$> _Show f suffix
    

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"

Upvotes: 6

Related Questions