我刚刚开始学习f#,并且对类型推断印象深刻,我想我会尝试从表中获取第一条记录的函数(使用查询表达式,Linq风格):
let getfirst data =
let result = query { for n in data do take 1 }
result |> Seq.head
有效,类型为IQueryable<'a> -> 'a
。
但是为什么这个版本不行呢?
let getfirst2 data =
query { for n in data do head }
for n in data do head
不应该像上次一样给出一个标量'a
吗?有人能解释为什么第二个版本不工作,以及如何使它不使用Seq.head工作?
原因是查询构建器有一个有点笨拙的重载Run
方法来运行查询,它具有以下重载:
QueryBuilder.Run : Quotations.Expr<'t> -> 't
QueryBuilder.Run : Quotations.Expr<Linq.QuerySource<'t, IEnumerable>> -> seq<'t>
QueryBuilder.Run : Quotations.Expr<Linq.QuerySource<'t, IQueryable>> -> IQueryable<'t>
在您的示例中,只要为data
指定合适的类型(尽管QuerySource<_,_>
是一种用户代码不打算使用的类型,因此不太可能出现两个重载),任何重载都可以应用。不幸的是,由于这些重载的定义方式很奇怪(第一个和第二个实际上是在单独的模块中定义的扩展方法),第三个重载在重载解析之战中获胜。
我不知道为什么,但是当你把鼠标悬停在getfirst2
的data
参数上时,你会看到它的类型是System.Linq.IQueryable<Linq.QuerySource<'a, System.Linq.IQueryable>>
,而实际上它应该是System.Linq.IQueryable<'a>
。
你可以通过添加类型注释来"修复"它:
open System.Linq
let getfirst2 (data : IQueryable<'a>) : 'a = query {
for item in data do
head
}
然后它像你所期望的那样工作:
[1 .. 10]
|> System.Linq.Queryable.AsQueryable
|> getfirst2
|> printfn "%d" // Prints 1.
也许有人能解释一下为什么编译器会推断它所做的类型