- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
这可能是一个非常基本的 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)。
Just
和
Nothing
值只是
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
堆栈,仍然使用
pure
和
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
来完成.与登录 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/
我有一个用正则表达式解析的字符串: "one [two] three [four] five" 我有正则表达式将括号内的文本提取到 中,但现在我想将其他内容(一、三、五)添加到 中, 但我希望有单
我是一名优秀的程序员,十分优秀!