#f# #null #pattern-matching
#f# #null #сопоставление с образцом
Вопрос:
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
не возвращает значение с нулевым значением, поэтому я не могу сопоставить с null
.
Итак, как проверить null
возвращаемое из функции в выражении соответствия?
Я использую FirstOrDefault, потому что я пытаюсь использовать простейший объект (Seq) из перечисляемого.
Я знаю, что могу использовать что-то еще, начиная с перечислимого, но все же мне хотелось бы понять, чего мне здесь не хватает.
[Решение]
Благодаря предложению @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"
Ответ №1:
Классы в 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
) вместо этого, который автоматически возвращает параметр, и вам не нужно внезапно вводить null
в свой код F #. Тогда ваш код в целом станет намного проще:
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. Вместо сопоставления с
null
и сопоставления сOption
, вы могли бы использоватьOption.ofObj
, который возвращаетNone
, если объект являетсяnull
иSome
в противном случае.2. @rob.earwaker, я подумал об этом, и в целом я бы согласился, но здесь это не помогло бы сразу, потому что тип, принадлежащий F #, не поддерживается
null
, и вы получите ошибку компиляции сOption.ofObj
. Это работает , если у вас есть тип CLI или вы добавилиAllowNullLiteral
, но это то, что вы можете добавлять только к классам, а не к записям / DU.