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)

这将与上面的示例完全相同。但从用户的角度来看,这样做的问题是,如果我们需要添加另一个参数,我们也必须更改map2为map3. 而且也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认同)