Search code examples
haskellmonadslazy-sequences

Monadic folding of Data.Text


I would like to write a function

countDigits :: Text -> Either Text (Map Int Int)

that builds a histogram of digit characters and fails if there are non-digit characters with a message that indicates the first or all non-digit characters. If this were with String I could write something like

countDigits = fmap frequencies . mapM toDigit
  where
    frequencies :: Ord a => [a] -> Map a Int
    frequencies = M.fromListWith (+) . (`zip` [0..9])

    toDigit :: Char -> Either String Int
    toDigit c = readEither [c] <> Left ("Invalid digit " ++ show c)

but since Data.Text is not Foldable, I cannot use mapM.

In fact, it seems a little difficult to convert a Data.Text value into any lazy stream value. (The folds of Data.Text.Strict are all eager and non-monadic, and Data.Text.Lazy has been warned against. Is this where one pulls out conduit or pipes?


Solution

  • Text can not be a Traversable since it is not parametrized with the type of its elements -- it always contains Chars, and nothing else. In other words, it is a "monomorphic container" instead of a polymorphic one.

    For "monomorphic containers" we have MonoTraversable which provides omapM:

    omapM :: Applicative m => (Element mono -> m (Element mono)) -> mono -> m mono 
    

    which means, in the case of Text,

    omapM :: Applicative m => (Char -> m Char) -> Text -> m Text