I recently had a problem with someone else’s code that raised a debugging issue not found in most languages. Somehow a complex let binding calling several other let bindings returned the wrong type signature. I was expecting a collection of generic type ‘a, but the compiler was inferring a collection of type int. It’s not unusual to have to explicitly annotate the parameter and return types in let bindings, and that was my first course of action, but the let binding still returned the wrong signature despite annotations. The compiler’s type inferencing works up and down the chain of functions, so how do you isolate a problem like this?
Here’s a technique you may find useful for problems like this. The key concept is to isolate the problem, so comment out all the code inside the let bindings in question and stub out a return of the correct type until the whole complex of functions returns the correct type. Then methodically start uncommenting code that returns a value, checking the let binding return type at each step. When the return type changes to incorrect, you have isolated the problem spot (or perhaps one of several).
In my case the author of the code in question incorrectly assigned the critical value of type ‘a from a wrong value, which happened to be of type int. His only unit test case tested type int, and no other types for the generic binding, so this problem slipped through.
Signature mis-matches can be very difficult to work through, so first try to isolate the problem by simplifying the chain of signature match-ups as much as you can.
Another lesson here is although functional programming eliminates whole classes of potential bugs, it is still possible to write incorrect logic. Unit testing only catches a problem if you wrote a (correct) unit test for that particular case.
More About Signatures
Type signatures is an interesting and important topic in its own right. Here’s some miscellaneous signature topics to brush up on.
How to read F# type signatures?
Equality and Comparison Constraints in F#