我正在创建一个访问MySQL数据库的模块。由于SQLProvider缺乏所需的功能,并且因为所述功能实际上非常简单,所以我决定基于通常的ADO.NET SqlConnection推出自己的功能。
为了使API不那么笨重,我使用了Tomas Petriceks idea和slight modifications
以下显示我遇到问题的核心部分
let internal connectionFor ta =
let conn = new DynamicSqlConnection(connectionStringFor ta)
conn.Open()
conn
let executeQuery queryString parameters map ta =
use conn = connectionFor ta
use cmd = conn %% queryString
List.iter (fun setter -> setter cmd) parameters
use r = cmd.ExecuteReader()
[| while r.Read() do yield map r |]
let getKolSkillInterests ta =
let map r =
{ Id = r?id
KolId = r?kol_id
SkillId = r?skill_id
SkillInterestTypeId = r?skill_interests_types_id }
executeQuery "SELECT * FROM tb_kol_skills_interests" [] map ta
let getCountryById ta i =
let query = "SELECT * FROM tb_lu_country WHERE id = @countryId"
let parameters = [ fun (c:DynamicSqlCommand) -> c?countryId <- i ]
let map r =
{ Id = r?id
Label = r?label
RegionId = r?region
Abbreviation = r?country_abbreviation
RealCountry = bigintToBool r?real_country }
executeQuery query parameters map ta
^^^ Error here
executeQuery
函数在交互模式下使用效果很好,但在上面的代码段中,getKolSkillInterests
编译并运行时,getCountryById
函数将不编译。在map
参数下executeQuery
出现了错误标记,错误为Type mismatch. The type PrintfFormat<'a,'b,'c','a,unit> is not compatible with the type string
我知道F#有时会有相当神秘的错误信息,但我不能理解这一点。
注意,如果我内联参数,如下所示 - 它可以工作
let getCountryById ta (i:int) =
executeQuery
"SELECT * FROM tb_lu_country WHERE id = @countryId"
[fun (c:DynamicSqlCommand) -> c?countryId <- i]
(fun r ->
{ Id = r?id
Label = r?label
RegionId = r?region
Abbreviation = r?country_abbreviation
RealCountry = bigintToBool r?real_country })
ta
答案 0 :(得分:2)
这些案件实际上并不“平等”。
我并不完全知道运算符%%
的作用,但是从您的错误消息中可以清楚地看到它期望PrintfFormat<...>
作为第二个参数,因此,通过类型推断的魔力, queryString
函数的executeQuery
参数也属于PrintfFormat<...>
类型。
现在,这种类型PrintfFormat<...>
很特别。它非常特殊,编译器本身在编译时提供了一种hack(各种各样的),这就是你如何使用printf
。试试这个:
printf "Abcd %d" 15 // Works just fine
let s = "Abcd %d"
printf s 15 // Same error as you're getting
为什么?事实证明,在F#中,字符串文字并不总是string
类型。有时,根据上下文,它们可以是PrintfFormat<...>
类型。如果编译器发现上下文需要PrintfFormat<...>
,它将编译字符串文字而不是string
。这就是为什么上面的第一个printf
调用有效,而第二个调用没有:符号s
已被推断为string
,因此它不能用作{printf
的参数1}},预计为PrintfFormat<...>
。
上面的示例很容易修复 - 您只需要让编译器了解您的预期类型:
let s: PrintfFormat<_,_,_,_> = "Abcd %d"
printf s 15 // Works now
因此您的代码同样可以修复:
let getCountryById ta i =
let query: PrintfFormat<_,_,_,_> = "SELECT * FROM tb_lu_country WHERE id = @countryId"
let parameters = [ fun (c:DynamicSqlCommand) -> c?countryId <- i ]
let map r =
{ Id = r?id
Label = r?label
RegionId = r?region
Abbreviation = r?country_abbreviation
RealCountry = bigintToBool r?real_country }
executeQuery query parameters map ta
P.S。当然,由于您实际上并未使用PrintfFormat
功能,因此您可能需要重新设计API以使其需要string
,从而使消费者更容易使用。
答案 1 :(得分:1)
我不是说那里的类型不匹配。 F#类型推断无法在没有明确注释的情况下键入所有类型良好的F#代码,因为它与经典的Hindley-Milner-Damas有太多偏差(对于#34; F#从左向右推断&#34;也是。)
我看到你的错误消息包含PrintfFormat,我怀疑它来自MySQL绑定,所以我不想详细介绍。但是我说f#的printf足够复杂,无论你是否提供类型注释都可能改变类型推断的方式。
请回想一下,内联函数参数本身是多态的,相当于向它们添加一些类型注释,因为当推断它们的类型时,你已经有了周围的上下文(在这种情况下,executeQuery的类型是,正如你所观察到的那样,已经推断。)同样,从上到下和从左到右的推断。
这就是为什么我关注提供越来越多的类型注释。