【问题标题】:What’s the idiomatic type to represent multiple failure cases but only one success case?表示多个失败案例但只有一个成功案例的惯用类型是什么?
【发布时间】:2018-10-08 18:04:57
【问题描述】:

Maybe a 类型表示可能失败的计算,其语义我们并不关心它究竟是如何失败的。如果计算成功,则可能返回 a 类型的任何值。

反之亦然,计算可能由于多种原因而失败(我们希望保留该信息),但成功不涉及除“是的,它成功了”以外的任何信息?我可以想到两种对这种计算进行编码的明显方法:

  • Maybe e,其中Just e 代表失败,Nothing 代表成功。这与Maybe 的通常用法大相径庭,以至于我不愿意使用它。
  • Either e (),其中Left e 代表失败,Right () 代表成功。这具有显式的优点,但具有……显式的缺点。写 () 感觉很尴尬,尤其是在类型签名的上下文之外。

Haskell 是否有更惯用的方式来表示“多个失败案例但只有一个成功案例”?

【问题讨论】:

  • 您正在寻找某种称为Result 的代数数据类型,由data Result = Ok | Error String 之类的东西定义。您可以更高级地使用类型变量来处理不同类型的错误,甚至可以在Ok 部分添加内容。 Elm 内置了这种类型。Haskell 有吗?它可能,环顾四周。 :)
  • 嗯,使用Either e () 并创建模式同义词pattern Success = Right () 怎么样?
  • 如果您正在使用单子计算,那么ExceptT err m () 将是我的第一直觉 - 一个操作成功但没有结果(成功)或产生错误。
  • 除了“是的,它成功了”之外不返回任何信息的计算在 IO 操作之外听起来不是很有用,据我所知,表示错误的最惯用方式这种情况是有例外的,不是吗?老实说,这真的取决于函数的作用。
  • System.Exit 定义了 data ExitCode = ExitSuccess | ExitFailure Int,这基本上是 Ray Toal 的 Result 建议的特定于退出状态的版本。 ExitCode 也有一个 Exception 实例,这意味着要做的“标准”事情是定义自己的域特定类型。

标签: haskell idioms


【解决方案1】:

如果没有看到实际代码,实际上很难理解失败的含义。如果它是一个纯函数,那么我看不出使用 Maybe 会有什么问题。我从来没有真正将Nothing 视为失败,但事实就是如此:没有。根据上下文,我要么返回 Nothing ,要么使用默认值并继续。我理解这可以被视为失败,但更多取决于观点 如果调用者比函数本身。 现在,您想要表示一个可能失败但不返回任何内容的计算。如果它是一个纯函数,那是没有意义的。你的功能是纯粹的,没有发生任何事情(没有副作用)并且你没有得到结果。所以在成功的情况下,你实际上什么也没计算:那不是成功,那什么都不是。 ATHI 如果你失败了,你有一个失败的原因。这与返回Maybe 的简单检查没有什么不同。

例如,您可能需要检查某个域是否不在黑名单中。为此,您在列表中进行查找:没有什么意味着它很好,即使这意味着它从您的角度来看是失败的并且需要停止您的计算。相同的代码可用于检查您的域是否属于白名单。在这种情况下,没有什么是失败的:只是取决于上下文。

现在,如果您正在运行一元操作(例如保存文件或其他内容),则不返回任何内容是有意义的,但可能会发生不同的故障(磁盘已满、路径不正确等)。我们不关心结果的 IO 的标准签名是 IO (),因此您可以选择 IO (Either e ())(每个人都会理解)或选择 IO () 并引发异常(如果它们真的异常)。

【讨论】:

  • Maybe 的 Monad 实现将 Nothing 视为失败。
【解决方案2】:

解决此问题的一种简单方法是将Either e ()pattern synonym 一起使用

pattern Success :: Either e ()  -- Explicit type signature not necessary
pattern Success = Right ()

如果它提高可读性,您还可以添加一些其他内容,例如

type FailableWith e = Either e ()

pattern FailedWith :: e -> FailableWith e
pattern FailedWith x = Left x

Maybe 不同,Either 的优势在于拥有所有现有机器:FunctorApplicativeMonadFoldableTraversableSemigroup 甚至OrdLeft x < Right y 应该始终保持)实例的行为可能已经完全符合您对错误处理的要求。一般来说,对于这种特殊情况,Maybe 往往会做与你想要的相反的事情(通常你想继续成功并在第一次失败后停止,这与大多数 Maybe 机器会做的相反提供这种情况)。

【讨论】:

  • 模式同义词有点少,因为只有当他已经知道Either e () 背后的语义时才会使用。使用Either e Successdata Success = Success 会更好。
  • @mb14 嗯,是什么让它变得更好?这只是将Right () 替换为Right Success 而不仅仅是Success。另外,我不确定我是否理解你在说什么。模式同义词和类型同义词的原因是为了让那些完全了解Either e ()意图的人更清楚。
  • 类型同义词不提供保证(或安全或语义,因为它们可能会消失)并添加不必要的间接级别。我认为Eithe e () 已经足够清楚了,但Eithe e Success 会在确实需要时强制执行语义。
【解决方案3】:

从问题中不清楚计算可能会如何失败。

如果它类似于编译器,可能会产生很多错误消息(而不是在第一个错误消息上停止),那么您需要类似:

type MyResult a = Either [Error] a

成功或失败并列出原因。

另一方面,如果您有一个不确定的计算,其中每个变体都可能成功或失败,那么您需要更多类似的东西:

type MyResult a = [Either Error a]

然后搜索结果列表。如果找到 Right 则返回它,否则组装 Lefts 列表。

【讨论】:

    猜你喜欢
    • 2013-11-23
    • 1970-01-01
    • 1970-01-01
    • 2015-12-22
    • 1970-01-01
    • 2013-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多