正确的方法强制转换可能a到a在榆树,没有明确失败的Nothings

bet*_*ros 9 maybe elm

好吧,我真正想做的是,我有一个数组,我想从中选择一个随机元素.显而易见的是从0和长度减1之间的随机数生成器获取一个整数,我已经工作了,然后应用Array.get,但返回一个Maybe a.(似乎还有一个包函数可以做同样的事情.)来自Haskell,我得到的类型意义是它保护我免受我的索引超出范围的情况,但我控制了索引而不是期待这种情况发生,所以我只想假设我得到了Just一些东西并且有点强行转换为a.在哈斯克尔fromJust,如果我感到冗长,那将是或者fromMaybe (error "some message").我应该怎么做榆树?

在邮件列表上发现了一个似乎正在讨论这个问题的讨论,但它已经有一段时间了,我没有在标准库中看到我想要的功能,而讨论表明它会是这样.

以下是我发现的一些非常不满意的潜在解决方案:

  • 只需使用默认.我确实有一个默认值aavailable,但我不喜欢这个,因为它给我的代码带来了完全错误的含义,并且可能会使调试变得更加困难.
  • 做一些摆弄端口与Javascript接口,并获得一个异常抛出,如果它没有.我还没有仔细研究它是如何工作的,但显然它是可能的.但这似乎混淆了过多的依赖关系,否则简单纯粹的榆树.

bet*_*ros 7

(回答我自己的问题)

我发现了两个更令人满意的解决方案:

  • 滚动我自己的部分定义的函数,该函数在链接讨论的其他地方引用.但代码有点不完整(我希望编译器会警告我某天不完整的模式匹配)并且错误信息仍然不清楚.
  • 模式匹配并使用Debug.crash,如果它是Nothing.这似乎与Haskell类似error,是我现在倾向于的解决方案.

    import Debug
    
    fromJust : Maybe a -> a
    fromJust x = case x of
        Just y -> y
        Nothing -> Debug.crash "error: fromJust Nothing"
    
    Run Code Online (Sandbox Code Playgroud)

    (尽管如此,模块名称和描述也让我犹豫不决,因为它看起来似乎不是用于我的目的的"正确"方法;我想指出真正的程序员错误而不仅仅是调试.)

  • 这正是这个名字对我的意思,但如果是这样,我怎么能在生产中定义`fromJust`? (2认同)

Mez*_*zza 5

解决方案

某个函数或等效函数的存在或使用fromJust实际上是代码味道,它会告诉您 API 的设计不正确。问题在于,您在获得执行该操作的信息之前就试图做出该做什么的决定。您可以在两种情况下考虑这一点:

  1. 如果您知道应该用 做什么Nothing,那么解决方案很简单:使用withDefault。当您查看代码中的正确位置时,这一点会变得显而易见。

  2. 如果您不知道在这种情况下应该做什么Nothing,但您仍然想做出改变,那么您需要采用不同的方法。Maybe而不是从使用中拉出值Maybe.map来更改值,同时保留Maybe. 举个例子,假设您正在执行以下操作:

    foo : Maybe Int -> Int
    foo maybeVal =
      let
        innerVal = fromJust maybeVal
      in
        innerVal + 2
    
    Run Code Online (Sandbox Code Playgroud)

    相反,你会想要这个:

    foo : Maybe Int -> Maybe Int
    foo maybeVal =
        Maybe.map (\innerVal -> innerVal + 2) maybeVal
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,在这种情况下,您想要的更改仍然完成,您只是没有处理Nothing. 现在,您可以在调用链中上下传递该值,直到到达可以自然使用来withDefault摆脱Maybe.

所发生的情况是,我们已经将“我如何更改这个值”和“当它不存在时我该怎么办?”的关注点分开了。我们使用 处理前者Maybe.map,使用 处理后者Maybe.withDefault

警告

在少数情况下,您只是知道自己有一个Just值,并且需要按照fromJust您所描述的方式消除它,但这些情况应该很少。实际上有相当多的公司有更简单的替代方案。

示例:尝试过滤列表并获取值。

假设您有一个Maybe需要其值的 s 列表。一个常见的策略可能是:

foo : List (Maybe a) -> List a
foo hasAnything =
  let
    onlyHasJustValues = List.filter Maybe.isJust hasAnything
    onlyHasRealValues = List.map fromJust onlyHasJustValues
  in
    onlyHasRealValues
Run Code Online (Sandbox Code Playgroud)

事实证明,即使在这种情况下,也有一些干净的方法可以避免fromJust。大多数具有包含映射和过滤器的集合的语言都有一种使用Maybe内置过滤器的方法。Haskell 有Maybe.mapMaybe,Scala 有flatMap,Elm 有List.filterMap。这会将您的代码转换为:

foo : List (Maybe a) -> List a
foo hasAnything =
  let
    onlyHasRealValues = List.filterMap (\x -> x) hasAnything
  in
    onlyHasRealValues
Run Code Online (Sandbox Code Playgroud)