惯用的 monadic 版本的 maximumBy 会是什么样子?

What would an idiomatic, monadic version of maximumBy look like?

如何获得有效容器的最大元素,其中要比较的计算属性也会触发效果?

必须有更易读的方式来做这样的事情:

latest dir = Turtle.fold (z (ls dir)) Fold.maximum

z :: MonadIO m => m Turtle.FilePath -> m (UTCTime, Turtle.FilePath)
z mx = do
    x <- mx
    d <- datefile x
    return (d, x)

我使用了重载版本而不是非重载版本 maximumBy 但后者似乎更适合临时属性选择。

如何更有条理地解决类似问题?

所以我对Turtle一无所知;不知道这是否适合 Turtle 生态系统的其余部分。但是既然你在评论中说服了我 maximumByM 值得手写,我会这样做:

maximumOnM :: (Monad m, Ord b) => (a -> m b) -> [a] -> m a
maximumOnM cmp [x] = return x -- skip the effects if there's no need for comparison
maximumOnM cmp (x:xs) = cmp x >>= \b -> go x b xs where
    go x b [] = return x
    go x b (x':xs) = do
        b' <- cmp x'
        if b < b' then go x' b' xs else go x b xs

我通常更喜欢事物的 *On 版本——它采用映射到 Orderable 元素的函数——而不是 *By 版本——它采用直接进行比较的函数。 maximumByM 类似,但类型类似于 Monad m => (a -> a -> m Ordering) -> [a] -> m a,但这可能会迫使您为每个 a 重做效果,我猜这不是您想要的。我发现 *On 更符合我想做的事情和我想要的性能特征。

使用 reducers 包可以 运行 对可折叠产品产生影响。

我不确定它是否正确,但它利用了现有的组合器和实例(Bounded (Maybe a) 除外)。

import Data.Semigroup.Applicative (Ap(..))
import Data.Semigroup.Reducer (foldReduce)
import Data.Semigroup (Max(..))
import System.IO (withFile, hFileSize, IOMode(..))

-- | maxLength
--
-- >>> getMax $ maxLength ["abc","a","hello",""]
-- 5
maxLength :: [String] -> (Max Int)
maxLength = foldReduce . map (length)

-- | maxLengthIO
--
-- Note, this runs IO...
--
-- >>> (getAp $ maxLengthIO ["package.yaml", "src/Lib.hs"]) >>= return . getMax
-- Just 1212
--
-- >>> (getAp $ maxLengthIO []) >>= return . getMax
-- Nothing
maxLengthIO :: [String] -> Ap IO (Max (Maybe Integer))
maxLengthIO xs = foldReduce (map (fmap Just . f) xs) where
    f :: String -> IO Integer
    f s = withFile s ReadMode hFileSize

instance Ord a => Bounded (Maybe a) where
    maxBound = Nothing
    minBound = Nothing

既然你已经熟悉了Fold,你可能想了解一下FoldM,这是相似的。

data FoldM m a b =
  -- FoldM step initial extract
  forall x . FoldM (x -> a -> m x) (m x) (x -> m b)

你可以写:

maximumOnM ::
  (Ord b, Monad m)
  => (a -> m b) -> FoldM m a (Maybe a)
maximumOnM f = FoldM combine (pure Nothing) (fmap snd)
  where
    combine Nothing a = do
      f_a <- f a
      pure (Just (f_a, a))
    combine o@(Just (f_old, old)) new = do
      f_new <- f new
      if f_new > f_old
        then pure $ Just (f_new, new)
        else pure o

现在您可以使用 Foldl.foldM 来 运行 列表(或其他 Foldable 容器)的折叠。与 Fold 一样,FoldM 有一个 Applicative 实例,因此您可以将多个有效折叠组合成一个,交错每个折叠的效果并组合它们的结果。