在看似相同的用例中输入不匹配

时间:2015-11-26 17:55:58

标签: types f# printf

我正在创建一个访问MySQL数据库的模块。由于SQLProvider缺乏所需的功能,并且因为所述功能实际上非常简单,所以我决定基于通常的ADO.NET SqlConnection推出自己的功能。

为了使API不那么笨重,我使用了Tomas Petriceks ideaslight 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

2 个答案:

答案 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的类型是,正如你所观察到的那样,已经推断。)同样,从上到下和从左到右的推断。

这就是为什么我关注提供越来越多的类型注释。