F#:在 System.Collections.Dictionary 中查找元素时出现奇怪的编译器错误

F#: weird compiler error when finding an element in a System.Collections.Dictionary

我知道在 F# 中有更漂亮的方法来解析 JSON(例如使用类型提供程序感谢 FSharp.Data),但为了简单起见(因为我在 fsx 文件中这样做而且我还不想处理 Nuget+Paket 用具),我在这里使用 System.Web.Script.Serialization.JavaScriptSerializer

问题是当我尝试使用以下函数在字典中查找元素时:

let isTestNet(): bool =
    let json = SomeFuncThatGivesMeAString()
    let jss = new JavaScriptSerializer()
    let dict = jss.DeserializeObject(json) :?> Dictionary<string, obj>
    for entry in dict do
        if (entry.Key.Equals("testnet")) then
            let result = entry.Value :?> bool
            result
    failwith "JSON response didn't include a 'testnet' element? " + json

编译器突出显示倒数第二行,并出现此错误:

error FS0001: This expression was expected to have type
    unit     
but here has type
    bool

怎么回事?我什至在函数头中指定了类型。为什么它需要一个单位?

for 表达式的计算结果应为 unit;让 fornon-unit 表达式结尾并不会以某种方式使其成为封闭函数的 return 值。最终你需要放弃 for.

一种选择是使用 Seq.tryFind 代替:

let isTestNet () : bool =
    let dict =
        let json = (* ... *)
        let jss = JavaScriptSerializer()
        jss.DeserializeObject json :?> Dictionary<string, obj>
    match dict |> Seq.tryFind (fun entry -> entry.Key.Equals "testnet") with
      | Some entry -> entry.Value :?> bool
      | _ -> failwith ("JSON response didn't include a 'testnet' element? " + json)

(N.b。由于运算符的优先级,错误消息的字符串连接必须括在括号中。)

虽然这看起来不错,但 Seq.tryFind 将执行 O(N) 搜索,而 Dictionary 本身将执行 O( 1) 直接使用时进行搜索,因此如果字典非常大,则此方法不可行。

效率更高,但不太明显(@AntonSchwaighofer 建议的改进除外):

let isTestNet () : bool =
    let dict =
        let json = (* ... *)
        let jss = JavaScriptSerializer()
        jss.DeserializeObject json :?> Dictionary<string, obj>
    match dict.TryGetValue "testnet" with
      | true, (:? bool as value) -> value
      | true, _ -> failwithf "'testnet' element was not a bool? %s" json
      | _ -> failwithf "JSON response didn't include a 'testnet' element? %s" json