首先,这可能与您的问题没有直接关系,但您可能希望重新整理此函数中的逻辑。
代替:
“我寻找与 fname、lastname 和 emai 匹配的客户;如果失败,我只寻找 fname + email,然后只寻找电子邮件,然后创建一个客人”
这样进行可能会更好:
“我查找匹配的电子邮件。如果我得到多个匹配项,我会查找匹配的 fname,如果再次出现多个,我会查找匹配的 lname”。
这不仅可以让您更好地构建代码,还可以迫使您处理逻辑中可能存在的问题。
例如,如果您有多个匹配的电子邮件,但没有一个具有正确的名称,该怎么办?目前,您只需选择序列中的第一个,这可能是您想要的,也可能不是您想要的,这取决于 Data.Customers() 的排序方式,如果它是排序的。
现在,如果电子邮件必须是唯一的,那么这将不是问题 - 但如果是这种情况,那么您不妨跳过检查名字/姓氏!
(我不愿提及它,但它也可能会在一定程度上加快您的代码速度,因为您不必多次检查相同字段的记录,也不会在电子邮件足够时检查其他字段。)
现在开始回答您的问题 - 问题不在于使用 Option,问题在于您实际上执行了 3 次相同的操作! (“查找匹配项,如果未找到,则查找后备”)。以递归方式重构函数将消除丑陋的对角线结构,和允许您在将来轻松扩展函数以检查其他字段。
对您的代码的其他一些小建议:
- 由于您只使用与
Foo 相同的参数调用 validFoo 辅助函数,因此您可以将它们烘焙到函数定义中以精简代码。
- 使用
.toLower()/.toUpper() 进行不区分大小写的字符串比较是很常见的,但由于它实际上会为每个字符串创建新的小写副本,因此有些不太理想。正确的方法是使用String.Equals(a, b, StringComparison.CurrentCultureIgnoreCase)。 99% 的情况下,这是一个无关紧要的微优化,但如果您拥有庞大的客户数据库并进行大量客户查找,那么这种功能实际上可能很重要!
- 如果可能,我会修改
createGuest 函数,使其返回整个customer 对象,并且只将.id 作为该函数的最后一行——或者更好的是,返回一个@987654330 @ 也来自此函数,并提供单独的单行 findCustomerId = findCustomer >> (fun c -> c.id) 以方便使用。
综上所述,我们有以下内容。为了这个例子,我假设在多个同样有效的匹配的情况下,你会想要 last,或者最近的一个。但你也可以抛出异常,按日期字段排序,或其他。
let findCustomerId fname lname email =
let (==) (a:string) (b:string) = String.Equals(a, b, StringComparison.CurrentCultureIgnoreCase)
let validFName = fun (cus:customer) -> fname == cus.firstname
let validLName = fun (cus:customer) -> lname == cus.lastname
let validEmail = fun (cus:customer) -> email == cus.email
let allCustomers = Data.Customers ()
let pickBetweenEquallyValid = Seq.last
let rec check customers predicates fallback =
match predicates with
| [] -> fallback
| pred :: otherPreds ->
let matchingCustomers = customers |> Seq.filter pred
match Seq.length matchingCustomers with
| 0 -> fallback
| 1 -> (Seq.head matchingCustomers).id
| _ -> check matchingCustomers otherPreds (pickBetweenEquallyValid matchingCustomers).id
check allCustomers [validEmail; validFName; validLName] (createGuest())
最后一件事:到处都是那些丑陋的(通常是 O(n))Seq.foo 表达式是必要的,因为我不知道 Data.Customers 返回什么样的序列,而一般的 Seq 类不是很对模式匹配友好。
例如,如果Data.Customers返回一个数组,那么可读性会显着提高:
let pickBetweenEquallyValid results = results.[results.Length - 1]
let rec check customers predicates fallback =
match predicates with
| [] -> fallback
| pred :: otherPreds ->
let matchingCustomers = customers |> Array.filter pred
match matchingCustomers with
| [||] -> fallback
| [| uniqueMatch |] -> uniqueMatch.id
| _ -> check matchingCustomers otherPreds (pickBetweenEquallyValid matchingCustomers).id
check allCustomers [validEmail; validFName; validLName] (createGuest())