gpt4 book ai didi

haskell - 使用连续的非此即彼/可能时减少嵌套

转载 作者:行者123 更新时间:2023-12-04 11:07:36 28 4
gpt4 key购买 nike

这可能是一个非常基本的 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 early return在任何阶段停止流程的语句。但这在 Haskell 中是不可用的(无论如何这看起来很不习惯,并且首先破坏了使用该语言的整个目的)。
然而,在处理链接连续 Either 的相同逻辑时,是否有可能在 Haskell 中实现相同的“平坦度”?/ Maybe一个接一个地?

-- EDIT following the duplicate suggestion:

I can see how the other question is related, but it's only that(related) — it doesn't answer the question exposed here which is howto flatten a 3-level nested case. Furthermore this question (here)exposes the problem in a much more generic manner than the other one,which is very use-case-specific. I guess answering this question(here) would be beneficial to other readers from the community,compared to the other one.

I understand how obvious it seems to be for seasoned Haskellers that"just use EitherT" sounds like a perfectly valid answer, but thepoint here is that this question is asked from the perspective ofsomeone who is not a seasoned Haskeller, and also who's read over andagain that Monad transformers have their limitations, and maybe Freemonad or Polysemy or other alternatives would be best, etc. I guessthis would be useful for the community at large to have this specificquestion answered with different alternatives in that regard, so thenewbie Haskeller can find himself slightly less "lost in translation"when starting to be confronted with more complex codebases.

最佳答案

为了“反向推断” 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 的事实。是一个单子(monad):
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'
总是绑定(bind) variableName有点别扭与 let提取前 variableName'从单子(monad)。这里实际上是没有必要的(您可以将 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'
重点是,你绑定(bind)到 weather 的东西本身可能在 Maybe单子(monad)。
避免本质上重复的变量名称的一种方法是使用 lambda-case 扩展,这允许您将其中一个 eta-reduce 去掉。此外, JustNothing值只是 pure 的一个特定示例和 empty , 你得到这个代码:
{-# 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 的效果单子(monad)。换句话说,你不想要 Maybe成为 monad,而是将其短路属性放在 IO 之上单子(monad)。因此,您转换 IO单子(monad)。你还可以 lift将普通旧的未转换 IO 操作转换为 MaybeT堆栈,仍然使用 pureempty对于可能性,因此得到与没有 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 来完成.与登录 IO 相比的优势在于,日志不仅会在某个地方结束,而且函数的调用者会知道日志已创建,并且可以决定是将其放入文件中,还是直接在终端上显示,或者干脆丢弃它。
但是由于您似乎总是只记录 Nothing在这种情况下,更好的选择是不使用 Maybe变压器除了 Except 相反,因为这似乎是您的想法:
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
最后——最后请注意,您实际上并不需要绑定(bind)中间变量,因为您总是只是将它们传递给下一个操作。即,您的组合链为 Kleisli arrows :
predict = withExcept (("could not get weather: "<>) . msg)
. getWeatherInfo
>=> withExcept (("could not craft query: "<>) . msg)
. except . craftQuery
>=> withExcept (("could not make prediction: "<>) . msg)
. makePrediction

关于haskell - 使用连续的非此即彼/可能时减少嵌套,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67617871/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com