Elm 'Json.Decode.succeed':如果它应该始终 return 相同的值,它如何在解码管道中使用?

Elm 'Json.Decode.succeed': how is it used in a decode pipeline if it is supposed to always return the same value?

我正在学习 Elm,令我困惑的一件事是 'Json.Decode.succeed'。根据docs

succeed : a -> Decoder a

Ignore the JSON and produce a certain Elm value.

decodeString (succeed 42) "true"    == Ok 42
decodeString (succeed 42) "[1,2,3]" == Ok 42
decodeString (succeed 42) "hello"   == Err ...

我明白了(虽然,作为初学者,我还没有看到它的用途)。但是这种方法用于解码管道,因此:

somethingDecoder : Maybe Wookie -> Decoder Something
somethingDecoder maybeWookie =
    Json.Decode.succeed Something
        |> required "caterpillar" Caterpillar.decoder
        |> required "author" (Author.decoder maybeWookie)

这是怎么回事?也就是说,如果 'succeed' 忽略传递给它的 JSON,它如何用于读取 JSON 并将其转换为 Elm 值?任何线索表示赞赏!

刚开始,解码器管道的直觉是它就像柯里化函数,其中管道 requiredoptional 逐个应用参数。期望一切,包括函数、它的参数 以及 return 值都包含在 Decoder 中。

举个例子:

succeed Something
  |> required (succeed 42)
  |> required (succeed "foo")

等同于

succeed (Something 42 "foo")

decodeString (succeed (Something 42 "foo")) whatever

将 return Ok (Something 42 "foo") 只要 whatever 有效 JSON.

当一切都成功时,这只是一个非常复杂的函数调用。解码器更有趣的方面,也是我们首先使用它们的原因,在于错误路径。但由于 'succeed' 是这里感兴趣的内容,我们将忽略它并节省大量时间、文本和脑细胞。只要知道,如果不考虑错误路径,这一切都会显得很做作。

无论如何,让我们尝试重新创建它,看看它是如何工作的。

解码.map2

除了管道运算符之外,管道的关键是 Decode.map2 函数。如果您尝试在不使用管道的情况下编写 JSON 解码器,那么您可能已经使用过它或它的兄弟。我们可以像这样使用 map2 来实现上面的示例:

map2 Something
  (succeed 42)
  (succeed "foo")

这与上面的示例完全一样。但是,从用户 POV 来看,这个问题是,如果我们需要添加另一个参数,我们还必须将 map2 更改为 map3。而且 Something 没有包含在解码器中,这很无聊。

调用包装在解码器中的函数

无论如何这是有用的原因是因为它使我们能够同时访问多个值,并且能够以我们想要的任何方式组合它们。我们可以使用它来调用 Decoder 中的函数,并在 Decoder 中使用参数,并将结果也包装在 Decoder:

map2 (\f x -> f x)
  (succeed String.fromInt)
  (succeed 42)

柯里化和部分应用

不幸的是,如果我们需要更多参数,这仍然存在需要更改 map 函数的问题。如果只有一种方法可以一次将一个参数应用于一个函数……就像我们有柯里化和部分应用一样。既然我们现在有办法调用包装在解码器中的函数,那么如果我们 return 一个部分应用的函数而不是稍后应用剩余的参数呢?

map2 (\f x -> f x)
  (succeed Something)
  (succeed 42)

将 return 变成 Decoder (string -> Something),所以现在我们只需冲洗并重复这个和最后一个参数:

map2 (\f x -> f x)
  (map2 (\f x -> f x)
    (succeed Something)
    (succeed 42))
  (succeed "")

Et voila,我们现在已经重新创建了 JSON 解码管道!虽然表面上看可能不像。

Ceci n'est pas une pipe

最后一个技巧是将 map2 与管道运算符一起使用。管道本质上定义为 \x f -> f x。看看这与我们一直在使用的函数有多相似?唯一的区别是参数被交换了,所以我们也需要交换我们传递参数的顺序:

map2 (|>)
  (succeed "")
  (map2 (|>)
    (succeed 42)
    (succeed Something))

然后我们可以再次使用管道运算符来达到最终形式

succeed Something
  |> map2 (|>)
      (succeed 42)
  |> map2 (|>)
      (succeed "")

现在应该很明显 required 只是 map2 (|>) 的别名。

仅此而已!