One of the first issues I faced when learning F# was finding out how to specify multiple parameters to a function. While this might sound obvious when learning a functional programming language, I had a few confronting moments that forced me to unlearn things before I could make any progress.
I wanted to create a function that wrapped the Contains method of the String class. In C#, it would be implemented like this:
public class StringHelper { public static Boolean Contains(String substr, String str) { return str.Contains(substr); } }
Calling this simple function is quite obvious:
StringHelper.Contains("pdf", "document.pdf")
My first attempt at writing the equivalent function in F# looked like this:
let contains (substr, str: string) =
str.Contains substr
I had to make a type annotation for the second argument because otherwise the F# compiler was unable to infer its type as a string. Calling this function looks like this:
contains ("pdf", "document.pdf")
All very nice. The code works and life is good. But as it turns out, there’s something going on with this implementation that I didn’t realize at the time. Turns out that the contains function isn’t a function that accepts two string arguments, but a single tuple argument of two strings!
Executing this code in the F# REPL shows the following type annotation for the contains function:
val contains : substr:string * str:string –> bool
I noticed a lot of examples where F# functions were being called without the braces and without commas separating the parameters. Calling the current implementation of the contains function this way gave me the following error:
contains "pdf" "document.pdf"
error FS0003: This value is not a function and cannot be applied
So instead, I removed the braces and comma from the function definition like so:
let contains substr str: string =
str.Contains substr
The compiler then gave me the following error message:
error FS0072: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved.
I had quite some head scratching going on before I was finally able to figure this out. Apparently the second argument needs to be enclosed with braces because of the explicit type annotation.
let contains substr (str: string) = str.Contains substr contains "pdf" "document.pdf"
This time the F# REPL shows the following type annotation for the contains function:
val contains : substr:string -> t:string –> bool
This wasn’t quite what I expected at the time. As a developer who mostly writes C#, JavaScript code, I noticed how using comma-separated parameter/argument lists within braces was so engrained in my ability to read and write code. Even when dabbling with Clojure in my spare time I got no shortage of braces either. Even when writing Ruby code, I held on to the habit of using comma-separated parameter/argument lists enclosed in braces. I told myself that this would improve the readability of my code. But in fact it was my brain trying to keep me in the comfort zone.
At this point I’m quite comfortable with this syntax in F#. But it definitely took some time getting used to not adding braces/commas all over the place. I must say that the Troubleshooting F# page on Scott Wlaschin’s website was a great help!
Until next time.