F#|如何在Match表达式中管理null非Nullable? [解决方案]

问题描述

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不返回Nullabe,因此我无法与null匹配。
那么,如何检查Match表达式中从函数返回的null

我使用的是FirstOrDefault,因为我尝试使用Enumerable中最简单的对象(Seq)。
我知道我可以使用Enumerable开头的其他内容,但我仍然想了解这里缺少的内容。

[解决方案]

感谢@Abel建议使用.tryFind(),我使用完成了任务 Seq.tryFind()返回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的实例,当您需要与可以返回null的代码进行互操作时,可以更轻松地在代码中使用它。

请注意,带有| 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),它会自动返回一个选项,而您不会不必在您的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)

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...