F# 查询表达式中运算符的顺序

order of operators in F# query expression

查询表达式运算符的顺序重要吗? Idk,但有时(在某些 selections 中)它会,但有时它不会(或者它可能会但隐式处理某些特定情况)。

是否强制要求 select 运算符必须放在最后?在几乎所有的组合中,如果你不把它写成最后一条语句,它就会抱怨,但是在 take n 的情况下,这个运算符可以在 [=35= 之后]

我只对执行过程如何运作感兴趣?

这让我想到另一个问题。如果它遍历 Iterable 集合,因此在第一次迭代时它 select 是某个(第一个)值,那么 order 如何作用于那个(第一个)值?如果它首先返回序列,然后在该序列上执行命令,那将很清楚。但似乎它在每次迭代时都执行 sortBy(?)。我对执行算法的设计很感兴趣。

这是我的查询表达式示例。

let sq = query {
   for p in datasource do
   where p.age>20
   sortBy p.age
   select p
}

如有解释,将不胜感激。

谢谢

不用猜也能知道

let sample = Seq.init 10 (fun i -> i * 10) |> Seq.map (fun i -> { age =  i }) 
let sq = query {
   for p in sample do
   where (p.age > 20)       
   sortBy p.age
   select p
}

sq |> Seq.toList |> ignore

生成的 IL(清理后)看起来像

IL_004e: newobj instance void Program/sq@16::.ctor(class [FSharp.Core]Microsoft.FSharp.Linq.QueryBuilder)
IL_0053: callvirt instance [...] For<class Program/Person,      
IL_005d: callvirt instance [...] Where<class Program/Person,    
IL_0067: callvirt instance [...] SortBy<class Program/Person,   
IL_0071: callvirt instance [...] Select<class Program/Person,   

假设我们改变sortBy的顺序

let sq = query {
   for p in sample do
   sortBy p.age
   where (p.age > 20)       
   select p
}

新的 IL 将是:

IL_006c: callvirt instance [...] For<class Program/Person,      
IL_0076: callvirt instance [...] SortBy<class Program/Person,   
IL_0080: callvirt instance [...] Where<class Program/Person,    
IL_008a: callvirt instance [...] Select<class Program/Person,   

您可以清楚地看到它遵循您定义查询的确切顺序。 这对 T-SQL 理解无关紧要,因为查询将由表达式访问者翻译,但对于对象查询,查询表达式对您来说几乎只是语法糖。

方法二:

您可以扩展查询表达式模块以包含副作用运算符。这只是 Interactive Extensions 的 DoAction 方法的一个端口。

module QueryExtensions =

    type QueryBuilderEx() =
        inherit Linq.QueryBuilder()

        [<CustomOperation("doAction", MaintainsVariableSpace = true)>]
        member __.Do(source : Linq.QuerySource<'T,System.Collections.IEnumerable>, action) =            
            new Linq.QuerySource<'T,System.Collections.IEnumerable>(source.Source |> Seq.map (fun v -> action(v); v))


let query = QueryExtensions.QueryBuilderEx()

现在您可以像这样调试订单了

let sq = query {
       for p in sample do
       sortBy p.age       
       where (p.age > 20)       
       doAction (printfn "Next -> %A")
       select p
    }

如果将其移到 where 上方,您会看到它反映了筛选前的那些记录。