F# Parameters – Why Not Annotate Function Parameters?

%fparameters

To make this question answerable, let's assume that the cost of ambiguity in the mind of a programmer is much more expensive then a few extra keystrokes.

Given that, why would I allow my teammates to get away with not annotating their function parameters? Take the following code as an example of what could be a far more complex piece of code:

let foo x y = x + y

Now, a quick examination of the tooltip will show you that F# has determined you meant for x and y to be ints. If that's what you intended, then all is well. But I don't know if that's what you intended. What if you had created this code to concatenate two strings together? Or what if I think you probably meant to add doubles? Or what if I just don't want to have to hover the mouse over every single function parameter to determine its type?

Now take this as an example:

let foo x y = "result: " + x + y

F# now assumes you've probably intended to concatenate strings, so x and y are defined as strings. However, as the poor schmuck who's maintaining your code, I might look at this and wonder if perhaps you had intended to add x and y (ints) together and then append the result to a string for UI purposes.

Certainly for such simple examples one could let it go, but why not enforce a policy of explicit type annotation?

let foo (x:string) (y:string) = "result: " + x + y

What harm is there in being unambiguous? Sure, a programmer could choose the wrong types for what they are trying to do, but at least I know they intended it, that it wasn't just an oversight.

This is a serious question… I am still very new to F# and am blazing the trail for my company. The standards I adopt will likely be the basis for all future F# coding, embedded in the endless copy-pasting that I am sure will permeate the culture for years to come.

So… is there something special about F#'s type inference that makes it a valuable feature to hold onto, annotating only when necessary? Or do expert F#-ers make a habit of annotating their parameters for non-trivial applications?

Best Answer

I don’t use F#, but in Haskell it is considered good form to annotate (at least) top-level definitions, and sometimes local definitions, even though the language has pervasive type inference. This is for a few reasons:

  • Reading
    When you want to know how to use a function, it’s incredibly useful to have the type signature available. You can simply read it, rather than trying to infer it yourself or relying on tools to do it for you.

  • Refactoring
    When you want to alter a function, having an explicit signature gives you some assurance that your transformations preserve the intent of the original code. In a type-inferred language, you may find that highly polymorphic code will typecheck but not do what you intended. The type signature is a “barrier” that concretises type information at an interface.

  • Performance
    In Haskell, the inferred type of a function may be overloaded (by way of typeclasses), which may imply a runtime dispatch. For numeric types, the default type is an arbitrary-precision integer. If you don’t need the full generality of these features, then you can improve performance by specialising the function to the specific type you need.

For local definitions, let-bound variables, and formal parameters to lambdas, I find that type signatures usually cost more in code than the value they would add. So in code review, I would suggest you insist on signatures for top-level definitions and merely ask for judicious annotations elsewhere.