我有以下函数检查customer数据源中a 的存在并返回id.这是使用该Option类型的正确/惯用方式吗?
let findCustomerId fname lname email =
let (==) (a:string) (b:string) = a.ToLower() = b.ToLower()
let validFName name (cus:customer) = name == cus.firstname
let validLName name (cus:customer) = name == cus.lastname
let validEmail email (cus:customer) = email == cus.email
let allCustomers = Data.Customers()
let tryFind pred = allCustomers |> Seq.tryFind pred
tryFind (fun cus -> validFName fname cus && validEmail email cus && validLName lname cus)
|> function
| Some cus -> cus.id
| None -> tryFind (fun cus -> validFName fname cus && validEmail email cus)
|> function
| Some cus -> cus.id
| None -> tryFind (fun cus -> validEmail email cus)
|> function
| Some cus -> cus.id
| None -> createGuest() |> fun cus -> cus.id
Run Code Online (Sandbox Code Playgroud)
当你缩进缩进时,它永远不会好,所以看看你能做些什么是值得的.
这是解决问题的一种方法,通过引入一个小帮助函数:
let tryFindNext pred = function
| Some x -> Some x
| None -> tryFind pred
Run Code Online (Sandbox Code Playgroud)
您可以在findCustomerId函数内部使用它来展平后备选项:
let findCustomerId' fname lname email =
let (==) (a:string) (b:string) = a.ToLower() = b.ToLower()
let validFName name (cus:customer) = name == cus.firstname
let validLName name (cus:customer) = name == cus.lastname
let validEmail email (cus:customer) = email == cus.email
let allCustomers = Data.Customers()
let tryFind pred = allCustomers |> Seq.tryFind pred
let tryFindNext pred = function
| Some x -> Some x
| None -> tryFind pred
tryFind (fun cus -> validFName fname cus && validEmail email cus && validLName lname cus)
|> tryFindNext (fun cus -> validFName fname cus && validEmail email cus)
|> tryFindNext (fun cus -> validEmail email cus)
|> function | Some cus -> cus.id | None -> createGuest().id
Run Code Online (Sandbox Code Playgroud)
这与此处概述的方法非常相似.
选项形成一个monad,它们也是monoidal,因为它们支持表单的两个函数
zero: Option<T>
combine: Option<T> -> Option<T> -> Option<T>
Run Code Online (Sandbox Code Playgroud)
计算表达式用于提供一种更好的monad工作方式,它们也支持monoid操作.因此,您可以实现以下计算构建器Option:
type OptionBuilder() =
member this.Return(x) = Some(x)
member this.ReturnFrom(o: Option<_>) = o
member this.Bind(o, f) =
match o with
| None -> None
| Some(x) -> f x
member this.Delay(f) = f()
member this.Yield(x) = Some(x)
member this.YieldFrom(o: Option<_>) = o
member this.Zero() = None
member this.Combine(x, y) =
match x with
| None -> y
| _ -> x
let maybe = OptionBuilder()
Run Code Online (Sandbox Code Playgroud)
其中Combine返回第一个非空Option值.然后,您可以使用它来实现您的功能:
let existing = maybe {
yield! tryFind (fun cus -> validFName fname cus && validEmail email cus && validLName lname cus)
yield! tryFind (fun cus -> validFName fname cus && validEmail email cus)
yield! tryFind (fun cus -> validEmail email cus)
}
match existing with
| Some(c) -> c.id
| None -> (createGuest()).id
Run Code Online (Sandbox Code Playgroud)
在可读性方面,一点点抽象可以走很长的路......
let bindNone binder opt = if Option.isSome opt then opt else binder ()
let findCustomerId fname lname email =
let allCustomers = Data.Customers ()
let (==) (a:string) (b:string) = a.ToLower () = b.ToLower ()
let validFName name (cus:customer) = name == cus.firstname
let validLName name (cus:customer) = name == cus.lastname
let validEmail email (cus:customer) = email == cus.email
let tryFind pred = allCustomers |> Seq.tryFind pred
tryFind (fun cus -> validFName fname cus && validEmail email cus && validLName lname cus)
|> bindNone (fun () -> tryFind (fun cus -> validFName fname cus && validEmail email cus))
|> bindNone (fun () -> tryFind (fun cus -> validEmail email cus))
|> bindNone (fun () -> Some (createGuest ()))
|> Option.get
|> fun cus -> cus.id
Run Code Online (Sandbox Code Playgroud)
更容易遵循,唯一的开销是一些额外的null检查.
另外,如果我是你,因为大多数这些功能都是如此的小/琐碎,我会inline明智地撒上.
首先,这可能与您的问题没有直接关系,但您可能想要重置此功能中的逻辑.
代替:
"我寻找一个匹配fname,lastname和emai的客户;如果没有,我只找fname +电子邮件,然后只是发送电子邮件,然后创建一个客人"
这样做可能会更好:
"我寻找匹配的电子邮件.如果我得到多个匹配,我会寻找匹配的fname,如果再次出现倍数,我会寻找匹配的lname".
这不仅可以让您更好地构建代码,还可以强制您处理逻辑中可能存在的问题.
例如,如果您有多个匹配的电子邮件,但没有一个具有正确的名称,该怎么办?目前,您只需选择序列中的第一个,这可能是您想要的,也可能不是您想要的,具体取决于Data.Customers()的排序方式(如果已订购).
现在,如果电子邮件必须是唯一的,那么这不会是一个问题 - 但如果是这种情况,那么你也可以跳过检查名字/姓氏!
(我不敢提及它,但它也可能会加速你的代码,因为你不必为同一个字段不必要地多次检查记录,当你只需要电子邮件就足够检查其他字段.)
现在回答你的问题 - 问题不在于使用Option,问题是你执行了三次基本相同的操作!("找到匹配,然后如果找不到则寻找后备").以递归方式重构函数将消除丑陋的对角线结构,并允许您在将来平凡地扩展函数以检查其他字段.
您的代码的一些其他小建议:
validFoo具有相同参数的辅助函数,因此Foo可以将它们烘焙到函数定义中以缩小代码..toLower()/ .toUpper()进行不区分大小写的字符串比较很常见,但稍微不理想,因为它实际上会为每个字符串创建新的小写副本.正确的方法是使用String.Equals(a, b, StringComparison.CurrentCultureIgnoreCase).99%的时间这是一个无关紧要的微观优化,但如果你拥有庞大的客户数据库并进行大量的客户查询,那么这就是它真正重要的功能!createGuest功能,使其返回整个customer对象,只有把.id这个函数的最后一行-或者更好的是,返回customer从该函数的欢迎,并提供独立的单行findCustomerId = findCustomer >> (fun c -> c.id)的便于使用.有了这些,我们有以下几点.为了示例,我将假设在多个同等有效匹配的情况下,您将需要最后一个或最新的匹配.但是你也可以抛出一个异常,按日期字段排序,或者其他什么.
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())
Run Code Online (Sandbox Code Playgroud)
最后一件事:那些丑陋的(通常是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())
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
257 次 |
| 最近记录: |