An interesting case study of F# vs. C# was my use of Printf.sprintf to substitute parameters into my SQL statement (no, the parameters did not come in from the user, I have heard of SQL injection attacks). The compiler kept complaining every time I tried to separately store the pre-interpolated SQL as a string. It kept telling me, through the handy "mouse over a variable for a type tooltip" F# VS feature, that the first parameter to Printf.sprintf was of type 'a Printf.string_format, or something like that. Eventually the message got through my thick noggin: at compile time Printf.sprintf somehow transforms its first string argument into a function that takes a series of specifically-typed parameters! I had two ways of doing the interpolation: use Printf.sprintf by storing the SQL as a static method that just returns the SQL with a type annotation of 'a Printf.string_format, or use the .Net String.Format method by storing the SQL as an immutable plain string member variable, initialized in the constructor. Either way, I'm storing the SQL as an immutable at the class level.
Some other little tidbits from the experience:
- The general form of the code was "class member function equals pattern match on argument evaluates to let-binding of intermediate step in let-binding of intermediate step in expression to save to a Page mutable property". In other words, for a class method, perform a data transformation through repeated function application and then use an expression with a side-effect to save the transformation result.
- Normally a single F# source file becomes a module, but if you want to go C#-style and use the file to declare a namespace with internal class definitions, just put a 'namespace' line at the top and only declare types (such as classes) at the file's top level.
- Watch out for nulls creeping into your code through variables from non-F# .Net APIs! The very handy ?? binary operator appears to return its left argument if it is not null and the right argument if it is. null can also be used in pattern matches.
- F# does not appear to support the usual class access modifiers as other .Net languages. Well, it supports only one, I guess: public. F# (and OCaml, I believe) use a different way to hide implementation: signature files, which seem a bit like a C++ header file containing prototypes.