使用连续的 Either/Maybe 时减少嵌套

2023-12-21

这可能是一个非常基本的 Haskell 问题,但让我们假设以下函数签名

-- helper functions
getWeatherInfo :: Day -> IO (Either WeatherException WeatherInfo)
craftQuery :: WeatherInfo -> Either QueryException ModelQuery
makePrediction :: ModelQuery -> IO (Either ModelException ModelResult)

将以上所有内容链接成一个的天真的方式predict day函数可以是:

predict :: Day -> IO (Maybe Prediction)
predict day = do
    weather <- getWeatherInfo day
    pure $ case weather of
        Left ex -> do
            log "could not get weather: " <> msg ex
            Nothing
        Right wi -> do
            let query = craftQuery wi
            case query of
                Left ex -> do
                    log "could not craft query: " <> msg ex
                    Nothing
                Right mq -> do
                    prediction <- makePrediction mq
                    case prediction of
                        Left ex -> do
                            log "could not make prediction: " <> msg ex
                            Nothing
                        Right p ->
                            Just p

在更命令式的语言中,人们可以这样做:

def getWeatherInfo(day) -> Union[WeatherInfo, WeatherError]:
    pass

def craftQuery(weather) -> Union[ModelQuery, QueryError]:
    pass

def makePrediction(query) -> Union[ModelResult, ModelError]:
    pass

def predict(day) -> Optional[ModelResult]:
    weather = getWeatherInfo(day)
    if isinstance((err := weather), WeatherError):
        log(f"could not get weather: {err.msg}")
        return None

    query = craftQuery weather
    if isinstance((err := query), QueryError):
        log(f"could not craft query: {err.msg}")
        return None

    prediction = makePrediction query
    if isinstance((err := prediction), ModelError):
        log(f"could not make prediction: {err.msg}")
        return None

    return prediction

可以说,它在很多方面都不太安全,也更笨重,但也可以说,更扁平。我可以看到主要的区别是在Python中我们可以(是否应该是一个不同的故事)使用make multiple Earlyreturn在任何阶段停止流程的语句。但这在 Haskell 中是不可用的(无论如何,这看起来非常不惯用,并且首先违背了使用该语言的全部目的)。

然而,在处理链接连续的相同逻辑时,是否有可能在 Haskell 中实现相同类型的“平坦度”Either/Maybe一个接一个地?

-- 按照重复的建议进行编辑:

我可以看到另一个问题是如何相关的,但仅此而已 (相关)-它没有回答这里暴露的问题,即如何 展平 3 层嵌套案例。此外这个问题(这里) 以比其他问题更通用的方式暴露问题, 这是非常特定于用例的。我想回答这个问题 (此处)对社区中的其他读者有益, 与另一位相比。

我理解对于经验丰富的 Haskellers 来说这是多么明显 ”只需使用 EitherT“听起来是一个完全有效的答案,但是 这里的要点是,这个问题是从以下角度提出的: 一个不是经验丰富的 Haskeller 的人,也是一个读过并阅读过的人 再次强调 Monad 转换器有其局限性,也许是免费的 monad 或 Polysemy 或其他替代品是最好的,等等。我想 这对于整个社区来说是有用的,拥有这个特定的 在这方面问题有不同的答案,所以 新手哈斯凯勒会发现自己稍微不那么“迷失在翻译中” 当开始面对更复杂的代码库时。


为了“反向推断”monad 转换器是正确的工具,请考虑不需要 IO 的情况(例如,因为天气信息来自内存中已有的静态数据库):

getWeatherInfo' :: Day -> Either WeatherException WeatherInfo
craftQuery :: WeatherInfo -> Either QueryException ModelQuery
makePrediction' :: ModelQuery -> Either ModelException ModelResult

你的例子现在看起来像

predict' :: Day -> Maybe Prediction
predict' day =
    let weather = getWeatherInfo' day
    in case weather of
        Left ex ->
            Nothing
        Right wi -> do
            let query = craftQuery wi
            in case query of
                Left ex ->
                    Nothing
                Right mq ->
                    let prediction = makePrediction' mq
                    in case prediction of
                        Left ex ->
                            Nothing
                        Right p ->
                            Just p

几乎任何 Haskell 教程都解释了如何将其扁平化,使用以下事实:Maybe是一个单子:

predict' :: Day -> Maybe Prediction
predict' day = do
    let weather = getWeatherInfo' day
    weather' <- case weather of
      Left ex -> Nothing
      Right wi -> Just wi
    let query = craftQuery weather'
    query' <- case query of
      Left ex -> Nothing
      Right mq -> Just mq
    let prediction = makePrediction' query'
    prediction' <- case prediction of
      Left ex -> Nothing
      Right p -> Just p
    return prediction'

总是绑定有点尴尬variableName with let提取之前variableName'来自单子。这里实际上是不必要的(你可以直接输入getWeatherInfo' day本身在case声明),但请注意,更普遍的是这种情况:

predict' :: Day -> Maybe Prediction
predict' day = do
    weather <- pure (getWeatherInfo' day)
    weather' <- case weather of
      Left ex -> Nothing
      Right wi -> Just wi
    query <- pure (craftQuery weather')
    query' <- case query of
      Left ex -> Nothing
      Right mq -> Just mq
    prediction <- pure (makePrediction' query')
    prediction' <- case prediction of
      Left ex -> Nothing
      Right p -> Just p
    return prediction'

重点是,你要绑定的东西weather本身可以在Maybe monad.

避免本质上重复的变量名称的一种方法是使用 lambda-case 扩展,这允许您将其中之一进行 eta 缩减。此外,Just and Nothing值只是一个具体示例pure and empty https://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Applicative.html#t:Alternative,用它你可以得到这个代码:

{-# LANGUAGE LambdaCase #-}

import Control.Applicative

predict' :: Day -> Maybe Prediction
predict' day = do
    weather <- pure (getWeatherInfo' day) >>= \case
      Left ex -> empty
      Right wi -> pure wi
    query <- case craftQuery weather of
      Left ex -> empty
      Right mq -> pure mq
    prediction <- pure (makePrediction' query) >>= \case
      Left ex -> empty
      Right p -> pure p
    return prediction

很好,但你不能在里面工作只是Maybe monad因为你也有以下的影响IO单子。换句话说,你不想要Maybe to bemonad,而是将其短路属性放在IO单子。因此你转换 the IO单子。你仍然可以lift https://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Monad-IO-Class.html#t:MonadIO普通的旧式未转换 IO 操作为MaybeT堆栈,并且仍然使用pure and empty出于可能的考虑,从而获得与没有 IO 时几乎相同的代码:

predict :: Day -> MaybeT IO Prediction
predict day = do
    weather <- liftIO (getWeatherInfo day) >>= \case
      Left ex -> empty
      Right wi -> pure wi
    query <- case craftQuery weather of
      Left ex -> empty
      Right mq -> pure mq
    prediction <- liftIO (makePrediction query) >>= \case
      Left ex -> empty
      Right p -> pure p
    return prediction

最后,您现在可以更进一步,还使用转换器层以更好的方式处理日志记录。可以用以下方法完成WriterT https://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Trans-Writer-Lazy.html。相对于IO日志的优点是日志不会直接结束某处,但是函数的调用者将知道创建了日志,并可以决定是否将其放入文件中或直接在终端上显示或直接丢弃它。

但因为你似乎总是只是记录Nothing情况下,更好的选择是不使用Maybe变压器除了Except https://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Trans-Except.html相反,因为这似乎是你的想法:

import Control.Monad.Trans.Except

predict :: Day -> ExceptT String IO Prediction
predict day = do
    weather <- liftIO (getWeatherInfo day) >>= \case
      Left ex -> throwE $ "could not get weather: " <> msg ex
      Right wi -> pure wi
    query <- case craftQuery weather of
      Left ex -> throwE $ "could not craft query: " <> msg ex
      Right mq -> pure mq
    prediction <- liftIO (makePrediction query) >>= \case
      Left ex -> throwE $ "could not make prediction: " <> msg ex
      Right p -> pure p
    return prediction

事实上,你的原语可能首先应该在那个 monad 中,然后它会变得更加简洁:

getWeatherInfo :: Day -> ExceptT WeatherException IO WeatherInfo
makePrediction :: ModelQuery -> ExceptT ModelException IO WeatherInfo

predict day = do
    weather <- withExcept (("could not get weather: "<>) . msg)
       $ getWeatherInfo day
    query <- withExcept (("could not craft query: "<>) . msg)
        $ except (craftQuery weather)
    prediction <- withExcept (("could not make prediction: "<>) . msg)
        $ makePrediction query
    return prediction

最后,最后请注意,您实际上并不需要绑定中间变量,因为您始终只是在下一个操作中传递它们。也就是说,你有一个组合链克莱斯利箭 https://en.wikipedia.org/wiki/Kleisli_category:

predict = withExcept (("could not get weather: "<>) . msg)
                   . getWeatherInfo
      >=> withExcept (("could not craft query: "<>) . msg)
                   . except . craftQuery
      >=> withExcept (("could not make prediction: "<>) . msg)
                   . makePrediction
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用连续的 Either/Maybe 时减少嵌套 的相关文章

随机推荐

  • Bootstrap Carousel 多个项目一次移动一个项目[重复]

    这个问题在这里已经有答案了 我尝试使用引导程序创建多个项目轮播 我的问题是它会同时移动所有 3 个项目 而不是仅移动一个 请在此处查看演示 http plnkr co edit Fl0HZaU5x5ZkPEVo87u3 p preview
  • 双重释放或腐败(fasttop)

    我的代码的以下部分在执行时给了我这条消息 检测到glibc a out 双重释放或损坏 fasttop 0x08e065d0 问题就在这里 temp2 first 基本上 当您释放 temp2 时 您首先释放 而不是此处分配的内存 temp
  • 无法将动态生成的图像从任何浏览器粘贴到 MS Word

    我有一个生成图像然后将其推送到浏览器的应用程序 图像显示完全没有困难 还可以右键保存 并且可以毫无问题地粘贴到Gimp等应用程序中 但不能粘贴到MS Word中 我摆弄了应用程序的各个方面 以确保内容类型和所有其他标题都正确 但这对粘贴图像
  • 如何设置上传文件的最大大小

    我正在使用 JHipster 开发基于 Spring Boot 和 AngularJS 的应用程序 我的问题是如何设置上传文件的最大大小 如果我尝试上传到大文件 我会在控制台中收到以下信息 DEBUG 11768 io 8080 exec
  • 如何将回调函数传递给 StreamController

    我想知道我正在创建这样的 StreamController class StreamController controller new StreamController onListen onListen onPause onPause o
  • 谷歌云存储交易?

    看来GCS没有任何交易机制 它是否正确 我希望能够进行长期交易 例如 如果我可以启动一个事务并指定过期时间 如果未在 X 时间内提交 它将自动回滚 那就太好了 然后我可以使用这个句柄插入对象 组合 删除等 如果一切顺利 发出 isCommi
  • UIView 的 contentScaleFactor 取决于实现drawRect:?

    我偶然发现了一件奇怪的事情 看起来像UIView s contentScaleFactor即使在 Retina 设备上也始终为 1 除非您实现drawRect 考虑这段代码 interface MyView UIView end imple
  • 为 iOS 应用内购买提供折扣代码

    所以我知道 iOS 中没有用于应用内购买的促销代码 我想知道的是 苹果会拒绝这种机制吗 提供两种应用内购买 一种是全价 例如 9 99 美元 另一种是折扣价 例如 7 99 美元 对于同一商品 当您点击 7 99 美元的价格时 系统首先会要
  • 如何使用向量 SSE 运算将图像像素数据的字节数组转换为灰度

    我在转换存储在中的图像数据时遇到问题byte array到灰度 我想使用矢量 SIMD 操作 因为将来需要编写 ASM 和 C DLL 文件来测量操作时间 当我阅读有关 SIMD 的内容时 我发现 SSE 命令是在 128 位寄存器上运行的
  • 使用 C# 生成 Word 文档

    给定一个邮寄地址列表 我需要打开一个现有的 Word 文档 该文档的格式适合打印标签 然后将每个地址插入到表的不同单元格中 当前的解决方案打开Word应用程序并移动光标以插入文本 但是 在阅读了安全问题以及与从 Web 应用程序打开较新版本
  • 将除法结果舍入到 c 中的下一个整数

    我编写代码来显示多个页面 每页最多 5 行 其中包含来自一个列表的人员 PRE page number of the page we want to show starting with 1 RETURNS pagenumber of th
  • sbt-buildinfo生成的对象无法被引用

    我正在使用前面提到的 sbt 插件来获取我正在开发的应用程序的版本 该项目有子模块 这是主要的build sbt lazy val abandon project in file aggregate base cli gui depends
  • 类似于Pyspark中set_index的方法

    pyspark DataFrame对象中类似于pandas DataFrame set index的方法是什么 你能建议一下吗 None
  • 在 VSTS 上归档存储库

    我们有一个最初是在 NET Framework 中构建的应用程序 我们通过 VSTS 上的存储库对其进行管理 我们现在将其转换为 NET Core 我想使用我们用于 NET Framework 版本的原始名称在 VSTS 上为其创建一个新存
  • 是否可以在 Swift 上创建常量文件?

    我有大约 10 个 Swift 3 应用程序 它们几乎相似 但有些字段在每个应用程序中都会发生变化 我希望这些值可以在整个程序中使用 例如 每个公司的名称 应用程序的主颜色等 这些值在整个程序中将保持不变 我想按应用程序创建一个常量文件 这
  • org.springframework.web.servlet.PageNotFound noHandlerFound 未找到带有 URI 的 HTTP 请求的映射

    我知道这个问题被问了很多次 我已经尝试过 所有可能的解决方案仍然存在问题 实际上 同一个项目在直接从 netbeans 部署的 Tomcat 8 中运行时出现 0 错误 我在 eclipse 中创建了一个新项目并部署在 Websphere
  • Angular Material 2:修复多行错误消息

    我在我的角度应用程序中使用角度材料 2 当我的表单输入字段错误消息超过一行时 我遇到了问题 这是照片 这是代码
  • Xcode 7 或 8 与 Pod 相关的问题,根本无法运行

    尝试运行应用程序时 Xcode 中的 pod 出现此错误 error A cryptographic verification failure has occurred 尝试重新安装 Pods repo 重新安装 Xcode 也不能在模拟器
  • 如何从多模块 Maven 项目构建可执行 jar?

    我是 Maven 的初学者 很多东西都不明白 我可以构建简单的可执行 jar 但如何将多模块 Maven 项目构建为可执行 jar 对我来说很神奇 所以 我有三个项目 家长
  • 使用连续的 Either/Maybe 时减少嵌套

    这可能是一个非常基本的 Haskell 问题 但让我们假设以下函数签名 helper functions getWeatherInfo Day gt IO Either WeatherException WeatherInfo craftQ