You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

maximumBy的惯用单子式实现是什么?如何处理带效应容器的最大值选取?

嘿,我来帮你搞定这两个Haskell相关的问题~

1. 如何实现maximumBy的单子式(monadic)版本?

标准库的maximumBy是纯函数式的:它接受一个纯的比较函数a -> a -> Ordering和一个列表,返回列表中的最大值。要实现它的monadic版本,我们需要让比较过程支持效应(比如IO、Reader等),函数签名和实现可以这样写:

安全版本(处理空列表)

maximumByM :: Monad m => (a -> a -> m Ordering) -> [a] -> m (Maybe a)
maximumByM _ [] = return Nothing
maximumByM cmp (x:xs) = foldM go (Just x) xs
  where
    go Nothing y = return (Just y)
    go (Just current) y = do
        ord <- cmp current y
        return $ case ord of
            GT -> Just current  -- 当前元素更大,保留
            _ -> Just y         -- 新元素更大或相等,替换

非空列表简化版

如果能保证输入列表一定非空(比如从目录读取的文件列表),可以写一个更简洁的版本,直接返回m a:

maximumByM' :: Monad m => (a -> a -> m Ordering) -> [a] -> m a
maximumByM' _ [] = error "maximumByM': 不能传入空列表"
maximumByM' cmp (x:xs) = foldM go x xs
  where
    go current y = do
        ord <- cmp current y
        return $ if ord == GT then current else y

核心思路是用foldM遍历列表,维护当前的最大值,每一步都通过monadic的比较函数判断是否需要更新最大值。

2. 从带效应的容器中取最大值(比较属性带效应)的更可读实现

看了你给出的代码,你现在是先把每个FilePath转换成(UTCTime, FilePath)元组(通过z函数触发datefile的IO效应),再用Fold.maximum取最大值——这种方式虽然可行,但确实不够直观,因为你需要先把所有元素转换一遍,而不是直接表达“比较两个文件的修改时间,取最新的那个”的逻辑。

结合你提到的maximumBy的需求,我们可以用上面的单子式maximumByM,或者针对Turtle的Shell容器写一个自定义的Fold,让逻辑更清晰:

方案一:用单子式maximumByM处理文件列表

先把Turtle的Shell FilePath转换成普通列表,再用maximumByM比较每个文件的修改时间:

import Turtle
import Data.Time.Clock (UTCTime)
import Control.Monad (foldM)

-- 复用刚才的maximumByM
maximumByM :: Monad m => (a -> a -> m Ordering) -> [a] -> m (Maybe a)
maximumByM _ [] = return Nothing
maximumByM cmp (x:xs) = foldM go (Just x) xs
  where
    go Nothing y = return (Just y)
    go (Just current) y = do
        ord <- cmp current y
        return $ case ord of
            GT -> Just current
            _ -> Just y

latest :: FilePath -> IO (Maybe FilePath)
latest dir = do
    files <- toList (ls dir)  -- 把Shell转换成IO [FilePath]
    maximumByM compareFileTime files
  where
    -- 带IO效应的比较函数:比较两个文件的修改时间
    compareFileTime a b = do
        timeA <- datefile a
        timeB <- datefile b
        return $ compare timeA timeB  -- 时间越晚,Ordering越大

方案二:自定义Turtle Fold(贴合流式处理)

如果不想把Shell转换成列表(毕竟Shell是流式的,大目录下更高效),可以写一个支持monadic比较的Fold:

import Turtle
import Data.Time.Clock (UTCTime)
import Control.Foldl (Fold(..))

maximumByFold :: Monad m => (a -> a -> m Ordering) -> Fold a (m (Maybe a))
maximumByFold cmp = Fold step initial extract
  where
    initial = return Nothing
    step Nothing x = return (Just x)
    step (Just current) x = do
        ord <- cmp current x
        return $ if ord == GT then Just current else Just x
    extract = id

latest :: FilePath -> IO (Maybe FilePath)
latest dir = fold (ls dir) (maximumByFold compareFileTime)
  where
    compareFileTime a b = do
        timeA <- datefile a
        timeB <- datefile b
        return $ compare timeA timeB

这个方案直接在Shell的流式处理中计算最大值,不需要一次性加载所有文件到内存,逻辑上也更直接:我们告诉程序“遍历目录下的每个文件,用带IO的时间比较规则,找出最新的那个”。

两种方案都比你原来的写法更贴合“临时属性选择”的需求——你不需要提前把所有文件和时间绑定成元组,而是直接在比较逻辑中处理属性的效应,代码可读性会高很多。

内容的提问来源于stack exchange,提问作者sevo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:56:47