1

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?

sshine
  • 15,635
  • 1
  • 41
  • 66
  • 1
    Your `frequencies` doesn't look right either: doesn't it always return `M.empty`? I think you want something more like `M.insertWith`. – amalloy Dec 04 '18 at 17:43
  • 1
    I don't know of anything inherently wrong with `Data.Text.Lazy`. Using it for lazy I/O leads to the same general problems as other lazy I/O, but just using it as a data structure shouldn't be a problem. – dfeuer Dec 04 '18 at 18:49

1 Answers1

1

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
chi
  • 111,837
  • 3
  • 133
  • 218
  • Wow, thanks. I didn't know this existed. So for my problem in question, since I was converting a `[Char]` into an `Either String [Int]` with `mapM toDigit`, and `omapM` produces an `Applicative m => m Text`, would you recommend using [`otoList`](https://hackage.haskell.org/package/mono-traversable-1.0.9.0/docs/Data-MonoTraversable.html#v:otoList) to achieve an intermediate list or stream of integers before incrementing the appropriate key-value pair in the `Data.Map` that I'm producing? – sshine Dec 04 '18 at 14:00
  • Never mind! I see [`ofoldM`](https://hackage.haskell.org/package/mono-traversable-1.0.9.0/docs/Data-MonoTraversable.html#v:ofoldM), too. – sshine Dec 04 '18 at 14:02
  • @SimonShine Yes, a fold is probably what you need. I think all standard functor/foldable/traversable functions are also available in their "mono" variants. – chi Dec 04 '18 at 14:06
  • 1
    @SimonShine, you should definitely prefer a fold (either from `MonoFoldable` or just the (lazy) `Data.Text.Strict.foldr`) to a traversal when you can get away with it. `otraverse` for `Text` converts to a string and back by simple unpacking and packing; it's really quite inefficient. Traversing in `Maybe` (if that was what you were actually after) could almost certainly be done better with a low-level custom function (roughly speaking, traversing in `MaybeT (ST s)` instead). But it's better to skirt the complication altogether in this case. – dfeuer Dec 04 '18 at 19:04