F#查询表达式的产出

4

我正在学习 F#,现在正在阅读有关计算表达式和查询表达式的内容,以便与SQL类型提供程序一起使用。我正在完成一些简单的任务,但在某个时候需要连接(Union)2个查询,我的第一个想法是,在查询表达式中像序列和列表中的yield一样执行相同的操作,如下所示:

query {
    yield! for a in db.As select { // projection }
    yield! for b in db.Bs select { // projection }
}

这是无效的代码,我的第二个方法是使用“concat”将它们连接起来:

seq {
    yield! query {...}
    yield! query {...}
}

或者使用Linq的Concat函数来实现,方法如下:(query {...}).Concat(query {...})。这个问题的答案来自于这个问题的回答。
以上两种方法都可以实现,但有一个区别,使用seq会执行2次SQL查询,而Concat只会执行一次,这是可以理解的。
那么我的问题是:为什么查询表达式不支持yield
编辑: 进一步调查后,我看到了MSDN文档,发现实现了YieldYieldFrom方法,但没有实现CombineDelay方法,这让我更加困惑了。
2个回答

3

yield!在查询中得到了一定程度的支持,可以像正常使用select一样使用:

query { 
    for x in [5;2;0].AsQueryable() do 
    where (x > 1)
    sortBy x
    yield! [x; x-1] 
 } |> Seq.toList // [2;1;5;4]

然而,总的来说,你不能任意地交错查询和序列操作,因为很难定义它们应该如何组合:

query {
    for x in [1;2;3] do
    where (x > 1)
    while true do // error: can't use while (what would it mean?)
    sortBy x 
}

同样地:
query {
    for x in [1;2;3] do
    where (x > 1)
    sortBy x 
    yield! ['a';'b';'c']
    yield! ['x';'y';'z'] // error
}        

这有点模糊,因为不清楚第二个yield!是在for循环中还是之后添加了一组元素。
因此,最好将查询视为查询,序列视为序列,即使这两种计算表达式都支持某些相同的操作。
通常,查询自定义运算符逐个元素地工作,因此表达联合或连接等内容会很麻烦,因为它们处理整个集合而不是单个元素。但如果您愿意,可以创建一个查询生成器,添加一个需要序列的concat自定义运算符,尽管可能会感到有些不对称:
open System.Linq

type QB() =
    member inline x.Yield v = (Seq.singleton v).AsQueryable()
    member inline x.YieldFrom q = q
    [<CustomOperation("where", MaintainsVariableSpace=true)>]
    member x.Where(q:IQueryable<_>, [<ProjectionParameter>]c:Expressions.Expression<System.Func<_,_>>) = q.Where(c)
    [<CustomOperation("sortBy", MaintainsVariableSpace=true)>]
    member x.SortBy(q:IQueryable<_>, [<ProjectionParameter>]c:Expressions.Expression<System.Func<_,_>>) = q.OrderBy(c)
    [<CustomOperation("select")>]
    member x.Select(q:IQueryable<_>, [<ProjectionParameter>]c:Expressions.Expression<System.Func<_,_>>) = q.Select(c)
    [<CustomOperation("concat")>]
    member x.Concat(q:IQueryable<_>, q') = q.Concat(q')
    member x.For(q:IQueryable<'t>, c:'t->IQueryable<'u>) = q.SelectMany(fun t -> c t :> seq<_>) // TODO: implement something more reasonable here

let qb = QB()

qb {
    for x in ([5;2;0].AsQueryable()) do
    where (x > 1)
    sortBy x
    select x
    concat ([7;8;9].AsQueryable())
} |> Seq.toList

1
这是一个关于F#查询表达式的优秀参考资料: https://msdn.microsoft.com/zh-cn/library/hh225374.aspx 尤其是,我认为该页面上的以下示例可以满足您的需求:
let query1 = query {
        for n in db.Student do
        select (n.Name, n.Age)
    }

let query2 = query {
        for n in db.LastStudent do
        select (n.Name, n.Age)
        }

query2.Union (query1)

我已经知道如何“连接/合并”这两个查询,这更多是一个哲学问题。既然已经可以做到了,为什么不能使用在其他计算表达式中(如SeqList)使用的传统yield!来完成呢? - Luiso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接