簡體   English   中英

F# 與查詢結果匹配。 有沒有一種優雅的方式來做到這一點?

[英]F# match with query results. Is there an elegant way to do this?

通過解析 json,我得到了 JObject 類型的結果:

let j = JObject.Parse x

我必須做的代碼是:

if j = null then
    ... do stuff
else if j["aa"] <> null then
    ... do stuff
else if j["bb"] <> null then
    ... do stuff
else if j["cc"] <> null and j["dd"] <> null then
    ... do stuff

有沒有一種干凈的方法來做這場比賽?

做類似的陳述

| _ when j.["error"] <> null ->

看起來不是很干凈。 這可以做得更好嗎?

  1. 為第一個非空值做一些事情:
    let j = JObject.Parse x
    let doSomething s = printf "%A" s
    if isNull j then
        ()
    else
        [ j.["aa"]; j.["bb"]; j.["cc"] ]
        |> List.tryFind (fun s -> s |> Option.ofObj |> Option.isSome)
        |> doSomething
  1. 或者為每個非空值做一些事情:
    let j = JObject.Parse x
    let doSomething s = printf "%A" s
    if isNull j then
        ()
    else
        [ j.["aa"]; j.["bb"]; j.["cc"] ]
        |> List.choose (fun s -> s |> Option.ofObj)
        |> List.iter doSomething
  1. 或者為第一個非空值做一些不同的事情(取決於哪個值是非空的):
    let j = JObject.Parse x
    let doSomethingA s = printf "%A" s
    let doSomethingB s = printf "%A" s
    let doSomethingC s = printf "%A" s
    if isNull j then
        ()
    else
        [ 
            j.["aa"], doSomethingA
            j.["bb"], doSomethingB
            j.["cc"], doSomethingC
        ]
        |> List.tryFind (fun (s, _) -> s |> Option.ofObj |> Option.isSome)
        |> Option.iter (fun (s, f) -> f s)

如果您創建一個返回匹配JToken的活動模式 ...

let (|NonNull|_|) prop (o : JObject) =
    o.[prop] |> Option.ofObj

你可以這樣寫:

let handleAA (a : JToken) = ()

match JObject.Parse "{}" with
| null -> () // ...
| NonNull "aa" a -> handleAA a
| NonNull "bb" b & NonNull "cc" c -> ()
| _ -> () // all other

更新

如果你需要更多的力量,Active Patterns galore...

let (|J|_|) prop (o : obj) =
    match o with
    | :? JObject as o -> o.[prop] |> Option.ofObj
    | _ -> None

let (|Deep|_|) (path : string) (o : obj) =
    let get t p = t |> Option.bind (fun t -> (``|J|_|``) p t)
    match o with
    | :? JToken as t ->
        path.Split('.') |> Array.fold get (Option.ofObj t)
    | _ -> None

...一些幫手...

let jV (t : JToken) = t.Value<string>()
let handle t = jV t |> printfn "single: %s"
let handle2 a b = printfn "(%s, %s)" (jV a) (jV b)

...解析 function...

let parse o =
    match JsonConvert.DeserializeObject o with
    | null -> printfn "null"
    | J "aa" a -> handle a
    | J "bb" b & J "cc" c -> handle2 b c
    | J "bb" b & J "dd"  _ -> handle b
    | Deep "foo.bar" bar & Deep "hello.world" world -> handle2 bar world
    | Deep "foo.bar" bar -> handle bar
    | o -> printfn "val: %A" o

...然后我們 go:

parse "null" // null
parse "42" // val: 42L
parse "{ aa: 3.141 }" // single: 3.141
parse "{ bb: 2.718, cc: \"e\" }" // (2.718, e)
parse "{ bb: 2.718, dd: 0 }" // single: 2.718
parse "{ foo: { bar: \"baz\" } }" // single: baz
parse "{ foo: { bar: \"baz\" }, hello: { world: \"F#|>I❤\" } }" // (baz, F#|>I❤)

您可以創建一個活動模式來匹配非空值...

let (|NonNull|_|) = function null -> None | v -> Some v

...這將允許以下內容。

if isNull j then
    //do stuff
else
    match j.["aa"], j.["bb"], j.["cc"], j.["dd"] with
    | NonNull aa, _, _, _ -> //do stuff
    | _, NonNull bb, _, _ -> //do stuff
    | _, _, NonNull cc, NonNull dd -> //do stuff

您可以為每個鍵制作一個操作列表,以便您可以為每個鍵統一應用 null 檢查邏輯。

let j = JObject.Parse x
let doStuff key value = printfn "%s=>%s" key value

如果你想為每個鍵應用 doStuff,你可以迭代。 這是您的示例,但沒有 else ,因此它對存在的每個鍵都執行此操作。

  ["aa", doStuff
   "bb", doStuff
   "cc", doStuff]
  |> List.iter (fun (key,action) -> 
    j.TryGetValue key
    |> snd
    |> Option.ofObj
    |> Option.iter (action key))

更緊密地匹配您的示例,其中您僅 doStuff 出現的第一個鍵可能使用選擇僅獲取有效值、操作。

  ["aa", doStuff
   "bb", doStuff
   "cc", doStuff]
   |> Seq.choose (fun (key,action) ->
      j.TryGetValue key
      |> snd
      |> Option.ofObj
      |> Option.map (fun v -> action key v))
   |> Seq.tryHead

如果存在匹配的鍵並且 doStuff 返回了值,則此版本還會返回應用的 doStuff 的結果。 這有點濫用 Seq 的惰性,只調用第一個值,但您也可以 map 到 function 調用 Seq.tryHead 的結果。

Option.ofObj 將變成 null -> None 和 v -> Some v

由於 JObject 是可枚舉的,因此您只需 map 就可以了

let j = JObject.Parse x |> Seq.map Option.ofObj
// j.["aa"] is an Option type now, map will only multiply the value by 3 if it exists
// otherwise it will return none. 
let aa = j.["aa"] |> Option.map (fun a -> a * 3)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM