F # optional arguments and overloading alternatives
In my F # application, I often have to search across strings of a string within a string, so I created a function with the appropriate mapping:
let indexOf (str:string) (value:string) startIndex =
match str.IndexOf(value, startIndex, StringComparison.OrdinalIgnoreCase) with
| index when index >= 0 -> Some index
| _ -> None
I don't like the fact that when I want to search from the beginning, I need to pass the redundant 0 as the starting index.
I am relatively new to both F # and functional programming, so I would like to know what is the preferred (cleanest) solution from a functional point of view?
-
Create two versions:
let indexOfFrom (str:string) (value:string) startIndex = (...) let indexOf str value = indexOfFrom str value 0
-
Use the parameter type:
let foundIndex = indexOf "bar foobar" "bar" (Some 4)
-
Create a dedicated discriminatory union:
type Position = | Beginning | StartIndex of index : int let foundIndex = indexOf "bar foobar" "bar" (Index 4)
-
Place the "indexOf" function inside the type and use the "classic" overload.
-
Place the indexOf function inside a type and use the optional F # arguments.
source to share
If you are defining functionality as F # functions, then I think using two separate functions (with reasonably descriptive names) is probably the best option you have. So I would go with your first option (I definitely prefer this option for defining discriminatory aggregation for this single purpose only):
let indexOfFrom (str:string) (value:string) startIndex = (...)
let indexOf str value = indexOfFrom str value 0
An alternative is to define the functionality as members of the type - then you can use both the overloading and the optional F # arguments, but you will need to access them using the fully qualified name String.IndexOf
. You could write something like:
type String =
static member IndexOf(str:string, value:string, startIndex) = (...)
static member IndexOf(str, value) = String.IndexOf(str, value, 0)
Or, using optional parameters:
type String =
static member IndexOf(str:string, value:string, ?startIndex) = (...)
Which is the best option?
-
If you are developing a functional API (like a domain specific language), then your option with two separate functions is probably the best choice.
-
If you want to create a good F # API, I think your option (multiple functions) or optional parameters are quite reasonable. Functions are fairly widely used in Deedle and F # Charting relies on optional arguments.
-
The advantage of using overloading is that the library will also be used well from C #. So, if you are thinking about calling a library from C #, this is almost the only option.
source to share
I think option 1 (with curry function) would be the simplest. Curried functions are fairly common in functional programming.
In options 2 or 3, you still have to pass an extra parameter to the search function from the beginning
Parameters 4 or 5 require additional overhead to create the type. This is sort of overkill for this simple task.
source to share