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

Cer*_*ean 4 json elm

我正在学习 Elm,让我困惑的一件事是“Json.Decode.succeed”。根据文档

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 ...
Run Code Online (Sandbox Code Playgroud)

我理解这一点(尽管作为初学者,我还没有看到它的用途)。但该方法用在解码管道中,因此:

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

这里发生了什么?也就是说,如果“succeed”忽略传递给它的 JSON,那么它如何用于读取 JSON 并将其转换为 Elm 值?任何线索表示赞赏!

gle*_*nsl 5

首先,解码器管道的直觉是它的行为就像一个柯里化函数,其中逐一传递required和应用参数。optional预期所有内容(函数、其参数返回值)都包含在Decoders 中。

举个例子:

succeed Something
  |> required (succeed 42)
  |> required (succeed "foo")
Run Code Online (Sandbox Code Playgroud)

相当于

succeed (Something 42 "foo")
Run Code Online (Sandbox Code Playgroud)

decodeString (succeed (Something 42 "foo")) whatever
Run Code Online (Sandbox Code Playgroud)

Ok (Something 42 "foo")只要whateverJSON 有效,就会返回。

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

不管怎样,让我们​​尝试重新创建它,看看它是如何工作的。

解码.map2

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

map2 Something
  (succeed 42)
  (succeed "foo")
Run Code Online (Sandbox Code Playgroud)

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

调用解码器中包含的函数

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

map2 (\f x -> f x)
  (succeed String.fromInt)
  (succeed 42)
Run Code Online (Sandbox Code Playgroud)

柯里化和部分应用

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

map2 (\f x -> f x)
  (succeed Something)
  (succeed 42)
Run Code Online (Sandbox Code Playgroud)

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

map2 (\f x -> f x)
  (map2 (\f x -> f x)
    (succeed Something)
    (succeed 42))
  (succeed "")
Run Code Online (Sandbox Code Playgroud)

瞧,我们现在重新创建了 JSON 解码管道!虽然表面上看起来可能不太像。

塞西不是烟斗

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

map2 (|>)
  (succeed "")
  (map2 (|>)
    (succeed 42)
    (succeed Something))
Run Code Online (Sandbox Code Playgroud)

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

succeed Something
  |> map2 (|>)
      (succeed 42)
  |> map2 (|>)
      (succeed "")
Run Code Online (Sandbox Code Playgroud)

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

这就是全部!

  • 彻底的解释格伦斯尔!对于 Elm 新手来说,“type alias Something = { foo : Int, bar : String }”会自动创建一个带有签名“Int -> String -> Something”的辅助函数“Something”,这一点可能并不明显。所以 `succeed Something` 具有签名 `Decoder (Int -> String -> Something)`。正如您上面所描述的,这个 get 会通过一些“map2”向下传输,最终给您一个“Decoder Something”。这在 Haskell 中也称为“apply”÷ (2认同)