有没有办法避免模式匹配

时间:2018-02-10 10:11:43

标签: f#

我有分层数据的SQL表:

enter image description here

最高层次结构为5。 例如,第4行和第5行是第1行的子节点。

我注意让query expression获得给定的儿童记录。现在我有pattern matching

let private queryForChild (db: dbml.MobileDataContext) id1 id2 id3 id4 id5 et = 

    match (id1, id2, id3, id4, id5) with
    | _, "", "", "", "" -> query {
                            for rows in db.ItemType do
                            where (rows.Id1 = id1 && rows.Id2 <> "" && rows.Id3 = "" && rows.Id4 = "" && rows.Id5 = "" && rows.EntityType = et)
                            select rows 
                           }
    | _,  _, "", "", "" -> query {
                            for rows in db.ItemType do
                            where (rows.Id1 = id1 && rows.Id2 = id2 && rows.Id3 <> "" && rows.Id4 = "" && rows.Id5 = "" && rows.EntityType = et)                                    
                            select rows
                           }
    | _,  _,  _, "", "" -> query {
                            for rows in db.ItemType do
                            where (rows.Id1 = id1 && rows.Id2 = id2 && rows.Id3 = id3 && rows.Id4 <> "" && rows.Id5 = "" && rows.EntityType = et)                                
                            select rows
                           }
    | _,  _,  _, _, "" -> query {
                            for rows in db.ItemType do
                            where (rows.Id1 = id1 && rows.Id2 = id2 && rows.Id3 = id3 && rows.Id4 = id4 && rows.Id5 <> "" && rows.EntityType = et)                         
                            select rows
                          }
    | _,  _,  _, _, _ -> query {
                            for rows in db.ItemType do
                            where (rows.Id1 = "-1")
                            select rows
                        }

我不喜欢它并且想知道有没有办法使用boolean operators重写它以避免pattern matching

2 个答案:

答案 0 :(得分:7)

确实无法在query计算中使用自定义函数,因为query计算的内部被引用(作为F# Quotation),然后(最终)转换为SQL和自定义函数无法如此翻译。

然而,与C#不同,F#确实在代码报价中提供了代码重用功能 - 它被称为“splicing”。

考虑一个例子:

let q = query { for x in listOfInts do yield x + 42 }
> q.Expression
val it : Expression = [1; 2; 3; ... ].Select(_arg1 => (_arg1 + 42))

让我说我真的不喜欢那边+ 42,我想把它抽象出来。好吧,我可以这样做:

let add42 = <@ fun i -> i + 42 @>
let q = query { for x in listOfInts do yield (%add42) x }

如果我们现在检查q.Expression,我们会发现它与之前的版本相同:

> q.Expression
val it : Expression = [1; 2; 3; ... ].Select(_arg1 => (_arg1 + 42))

这是这里发生的事情。 add42是一个代码引用,包含一个向其参数添加42的函数。 %add42表达式“插入”(又名“拼接”)引号在较大引号的中间,产生如下表达式:

let q = query { for x in listOfInts do yield (fun i -> i + 42) x }

此表达式在从F#代码引用转换为System.Linq.Expressions.Expression期间得到简化,从而产生与第一个版本相同的表达式。

要添加的最后一部分:拼接引号不必是“常量”,它们也可以由函数生成。在构建整体报价期间对这些函数进行评估,然后对它们的结果进行拼接。例如,我可以像这样重新定义上面的代码:

let add a = <@ fun x -> x + a @>
let q2 = query { for x in list do yield (% add 42) x }

现在add是一个以42为参数并生成包含另一个函数的代码报价的函数。呼!

现在我们可以将所有这些应用到您的案例中:使自己成为一个函数,将idx作为参数并生成函数的引用,该函数在拼接后将应用于row.idx

// NOTE: I'm not sure if this logic is correct. You'll have to verify it.
//
// For the i-th ID:
//    * if all previous IDs are non-empty, 
//      but the i-th ID itself is empty, 
//      then the condition should check for i-th ID being non-empty.
//      This means "query rows of i-th level".
//    * if all previous IDs are non-empty, 
//      and the i-th ID itself is non-empty,
//      then the condition should check for i-th ID being equal to
//      This means "query rows of j-th level", where j > i
//    * Otherwise, the condition should check for
//      the i-th ID being empty.
//      This means "query rows of j-th level", where j < i
let compare prevIds thisId =
    if List.all ((<>) "") prevIds 
        then if thisId = ""
               then <@ fun id -> id <> "" @>
               else <@ fun id -> id = thisId @>
        else <@ fun id -> id = "" @>

let private queryForChild (db: dbml.MobileDataContext) id1 id2 id3 id4 id5 et = 
    query {
        for rows in db.ItemType do
        where (
           (% compare [] id1) rows.Id1 &&
           (% compare [id1] id2) rows.Id2 &&
           (% compare [id1; id2] id3) rows.Id3
           (% compare [id1; id2; id3] id4) rows.Id4
           (% compare [id1; id2; id3; id4] id5) rows.Id5 && 
           rows.EntityType = et )
        select rows 
    }

另请注意,您构建函数的方式,其行为未定义为具有“漏洞”的输入 - 即id1="x"id2=""id3="y" - 您是否要查询在这种情况下的第二或第四级?我会建议一个更好的数据结构,排除无意义的输入。

答案 1 :(得分:2)

也许,你可以使用这样的东西:

let equalOrNotEmpty a b = match a with | "" -> b <> "" | a -> a = b

并且用法是

let private queryForChild (db:dbml.MobileDataContext) id1 id2 id3 id4 id5 =
    query {
      for rows in db.ItemType do
      where (equalOrNotEmpty rows.Id1 id1 
             && equalOrNotEmpty rows.Id2 id2 
             && equalOrNotEmpty rows.Id3 id3 
             && equalOrNotEmpty rows.Id4 id4 
             && equalOrNotEmpty rows.Id5 id5 
             && rows.EntityType = et)                         
      select rows

}