使用其中任何一种时如何对条件进行排序

How to sequence conditions when using either

所以我正在构建这个 CLI todos 应用程序来学习一些 Haskell 并且我正在尝试简化错误处理(我觉得使用我当前的方法的代码太多了)。

所以我编写了这个函数,通过在特定位置应用函数来更新任务:

  updateTodoAt :: Int -> (Todo -> Either Error Todo) -> [Todo] -> Either Error [Todo]
  updateTodoAt position fn todos
                  | n < 0 = Left "Must be a strict positive, bruh!"
                  | n >= length todos = Left "Out of bounds?"
                  | otherwise = fn (todos!!n)
                                >>= (\todo -> Right(take n todos ++ [todo] ++ drop (n+1) todos))

这工作正常,但我觉得我遗漏了一些东西,应该有一种方法可以使用一些 *>>>>>= 来编写它,但我可以' 似乎能够找到组合。我刚刚开始掌握如何将这些链接在一起......:) 尝试完成此操作的原因是因为此函数正在另一个执行它自己的预验证的函数中调用,并且代码已经很混乱。

我在下一个块的行中尝试了各种排列(这只是一个例子),但从一开始我就觉得这种方法行不通,因为这不是 "cast" 从 Maybe 到 Either。

  updateTodoAt position fn todos = fuu n < 0 "Must be a strict positive, bruh!"
                    *> fuu (n >= length todos) "Out of bounds?"
                    *> fn (todos!!n)
                    >>= (\todo -> Right(take n todos ++ [todo] ++ drop (n+1) todos)) 
                  where n = position - 1

  fuu :: Bool -> Error -> Maybe Error
  fuu True e = Just e
  fuu False _ = Nothing

我的直觉告诉我,应该有一个可以像这样链接的函数。

供参考:type Error = Text,我正在使用 Protolude 和 OverloadedStrings

代码在 https://github.com/ssipos90/todos-hs,但有点“过时”。我已经开始使用 megaparsec 解析 "database" 文件,但我认为错误处理更为重要。

您要查找的 fuu 的定义是:

fuu :: Bool -> Error -> Either Error ()
fuu True e = Left e
fuu False _ = Right ()

原因如下...运算符 (*>) 的类型为:

(*>) :: (Applicative f) => f a -> f b -> f b

在此上下文中,f 专用于 Either Error,因此它实际上是:

(*>) :: Either Error a -> Either Error b -> Either Error b

如果你想写fuu p "xxx" *> other_action,结果的类型将是other_action的类型(即Either Error b对某些b)。您需要定义 fuu,以便提供的 fuu p "xxx" 具有某种类型 a 的类型 Either Error a,其中 a 是什么类型并不重要:

fuu :: Bool -> Error -> Either Error ???
fuu True err = Left err
fuu False _ = Right ???

如果您发现自己处于需要提供值和类型无关紧要的 ??? 的情况,value/type () 始终是一个不错的选择:

fuu :: Bool -> Error -> Either Error ()
fuu True err = Left err
fuu False _ = Right ()

注意一般写成:

when (n < 0) (Left "Must be a strict positive, bruh!")

使用 Control.Monad 中的 when,而不是定义 fuu.

此外,您可能会发现用 do-notation 重写后的函数看起来更自然了:

import Control.Monad

updateTodoAt' :: Int -> (Todo -> Either Error Todo) 
     -> [Todo] -> Either Error [Todo]
updateTodoAt' position fn todos = do
  when (n < 0) $ Left "Must be a strict positive, bruh!"
  when (n > length todos) $ Left "Out of bounds?"
  todo <- fn (todos !! n)
  return $ take n todos ++ [todo] ++ drop (n+1) todos
  where n = position - 1

你只需要在这里使用fmap

updateTodoAt :: Int -> (Todo -> Either Error Todo) -> [Todo] -> Either Error [Todo]
updateTodoAt n fn todos
  | n < 0 = Left "Must be a strict positive, bruh!"
  | n >= length todos = Left "Out of bounds?"
  | otherwise = let (l, (todo:r)) = splitAt n todos
        in fmap (\todo -> l ++ [todo] ++ r) (fn todo)

不要使用 !!。实际上只有两种错误情况:

  1. n为负数
  2. 输入列表为空(这意味着非负 n 太大)

给定一个非负 n 和一个非空列表,您要么将函数应用于第一个列表元素,要么递归。

updateTodoAt n _ xs
    | n < 0 = Left "negative index"
    | null xs = Left "index too large"
updateTodoAt 0 fn (x:xs) = (: xs) <$> (fn x)
updateTodoAt n fn (x:xs) = (x :) <$> updateTodoAt (n-1) fn xs