F# |如何在 Match 表达式中管理 null(不可为 Nullable)?

F# | How to manage null (not Nullable) in a Match expression?

open System.Linq

type Car(model:string, color:string) =
    member this.Model = model
    member this.Color = color
    
    member this.ToString() = sprintf "ModeL:%s Color:%s" model color
    
let cars = [
    Car("Ferrari", "red")
    Car("BMW", "blu")
]

let getCar model = 
    match cars.FirstOrDefault(fun c -> c.Model = model) with
    | car -> Some(car)                       // matches ALWAYS !
    //| car when car <> null -> Some(car)
    //| car when car <> (default(Object)) -> Some(car)
    //| null -> None
    //| Null -> None     

let mercedes = getCar("Mercedes")

let car = match mercedes with
          | Some c -> c.ToString()           // c is null !!!
          | _ -> "not found"

FirstOrDefault 不是 return Nullabe,所以我无法匹配 null
那么,如何检查匹配表达式中函数的 null return?

我正在使用 FirstOrDefault,因为我尝试使用 Enumerable 中最简单的对象 (Seq)。
我知道我可以使用从 Enumerable 开始的其他东西,但我仍然想了解我在这里缺少的东西。

[解决方案]

感谢@Abel 建议使用 .tryFind() 我完成了任务 Seq.tryFind() return 是 Car option

let getCar model = 
    let cars = lazy(
       // this.Collection.Indexes.List().ToEnumerable()   // this is the real data I'm using (MongoDB indexes of a collection)
       // |> Seq.map parseIndex  // a function that create Car (Index) from the BsonDocumentBsonDocument
       cars.AsEnumerable()        
    )
    cars.Value |> Seq.tryFind(fun c -> c.Model = model)
    
let mercedes = match getCar("Mercedes") with
               | Some c -> c.ToString()
               | _ -> "not found"
         
let ferrari = match getCar("Ferrari") with
              | Some c -> c.ToString()
              | _ -> "not found"   
F# 中的

类 不能将 null 作为适当的值(这是 F# 更强大的方面之一)。但是,您可以通过添加 AllowNullLiteral 属性来打破此约定:

[<AllowNullLiteral>]
type Car(model:string, color:string) =
    member this.Model = model
    member this.Color = color

现在您可以创建 null 的实例,当您需要与可以 return null 的代码互操作时,在代码中使用它会更容易。 =27=]

请注意,带有 | car -> 的代码是一个变量模式,这意味着它捕获所有内容并将值分配给变量 car。不确定你想在这里做什么,但是 类 上的模式匹配不是很有用。

如果您需要匹配 null,将其作为第一个匹配项,第二个匹配项可以是 car,捕获其他所有内容。您的代码将变为:

[<AllowNullLiteral>]
type Car(model:string, color:string) =
    member this.Model = model
    member this.Color = color
    
    member this.ToString() = sprintf "ModeL:%s Color:%s" model color
    
module X = 
    let cars = [
        Car("Ferrari", "red")
        Car("BMW", "blu")
    ]

    let getCar model = 
        match cars.FirstOrDefault(fun c -> c.Model = model) with
        | null -> None
        | car -> Some(car)      // matches everything else

关于您的代码的另一个注意事项:Car 类型也可以作为记录创建:

type Car =
    { 
        Model: string
        Color: string
    }

而不是使用 LINQ,使用 List.tryFind(或者 Seq.tryFind,如果你想使用 IEnumerable)更惯用,它会自动 return 是一个选项,您不必在 F# 代码中突然引入 null。您的代码总体上会变得更简单:

type Car =
    { 
        Model: string
        Color: string
    }
    
    override this.ToString() = sprintf "ModeL:%s Color:%s" this.Model this.Color
    
module X = 
    let cars = [
        { Model = "Ferrari"; Color = "red" }
        { Model = "BMW"; Color = "blu" }
    ]

    let getCar model = cars |> List.tryFind (fun x -> x.Model = model)