Reputation: 646
I'm creating a module to access a MySQL database. Since SQLProvider fell short in needed functionality, and since said functionality is in fact pretty simple, I decided to roll my own based on the usual ADO.NET SqlConnection.
To make using the API less clunky, I used Tomas Petriceks idea with some slight modifications
The following shows the central parts of problem I have
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
The executeQuery
function works well, when used in interactive mode, however in the above snippet, while getKolSkillInterests
compiles and works, the getCountryById
function will not compile. The squiglly marks occur under the map
parameter to executeQuery
with the error Type mismatch. The type PrintfFormat<'a,'b,'c','a,unit> is not compatible with the type string
I know F# has pretty cryptic error messages at times, but I cannot grok this one.
Note, if I inline the parameters, as shown below - it works
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
Upvotes: 1
Views: 110
Reputation: 8001
I'm not saying there's type mismatch. F# type inference cannot type all well typed F# code without some explicit annotation because of too much deviation from classical Hindley-Milner-Damas (Also Google for "F# left to right inference".)
I see your error msg contains PrintfFormat which, I suspect, originated from MySQL binding, so I won't go into the full details. But I'd say (s)printf of F# is complex enough that whether you gave type annotation or not might change how type inference goes.
Please recall that inlining function arguments which are themselves polymorphic is equivalent to adding some type annotations to them, because when their types are inferred, you already have the surrounding context (executeQuery in this case whose type was, as you observed, already inferred.) Again, top-to-bottom and left-to-right inference.
This is why I'm concerned with giving more and more type annotations.
Upvotes: 1
Reputation: 80744
These cases are not actually "equal".
I don't exactly know what operator %%
does, but from your error message it is clear that it expects a PrintfFormat<...>
as second argument, and therefore, through the magic of type inference, the queryString
parameter of your executeQuery
function is also of type PrintfFormat<...>
.
Now, this type PrintfFormat<...>
is special. It is so special that the compiler itself provides a hack (of sorts) when compiling it, and that's how you can use printf
. Try this:
printf "Abcd %d" 15 // Works just fine
let s = "Abcd %d"
printf s 15 // Same error as you're getting
Why is that? Turns out, in F#, string literals are not always of type string
. Sometimes, depending on the context, they can be of type PrintfFormat<...>
. If the compiler sees that the context requires PrintfFormat<...>
, it will compile string literal as that instead of string
. That's why the first printf
call above works and the second one doesn't: the symbol s
has already been inferred as string
, and so it doesn't work as argument for printf
, which is expected to be PrintfFormat<...>
.
The example above is easily fixable - you just need to give the compiler a hand in figuring out your intended types:
let s: PrintfFormat<_,_,_,_> = "Abcd %d"
printf s 15 // Works now
As so your code is similarly fixable:
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. Of course, since you're not actually using the PrintfFormat
capabilities, you may want to redesign your API so that it takes a string
, thus making it easier for the consumer.
Upvotes: 2