Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# optional arguments and overloading alternatives

In my F# application I often need to perform a case-insensitive search of a string within a string, so I created a function with the appropriate comparison:

let indexOf (str:string) (value:string) startIndex =
    match str.IndexOf(value, startIndex, StringComparison.OrdinalIgnoreCase) with
    | index when index >= 0 -> Some index
    | _ -> None

I do not like the fact, that when I want to search from the beginning, I have to pass the redundant 0 as the start index.

I am relatively new to both F# and the functional programming, so I would like to know what is the preferred (cleanest) solution from the functional point of view?

  1. Create two versions:

    let indexOfFrom (str:string) (value:string) startIndex = (...)
    let indexOf str value = indexOfFrom str value 0
    
  2. Use Option type:

    let foundIndex = indexOf "bar foobar" "bar" (Some 4)
    
  3. Create a dedicated discriminated union:

    type Position =
        | Beginning
        | StartIndex of index : int
    let foundIndex = indexOf "bar foobar" "bar" (Index 4)
    
  4. Place the 'indexOf' function inside a type and use the 'classic' overloading.

  5. Place the 'indexOf' function inside a type and use F# optional arguments.

like image 222
Maciej Wozniak Avatar asked Nov 15 '14 23:11

Maciej Wozniak


2 Answers

If you are defining the functionality as F# functions, then I think that using two separate functions (with reasonably descriptive names) is probably the best option you have. So I'd go with your first option (I definitely prefer this option over defining a discriminated union just for this single purpose):

let indexOfFrom (str:string) (value:string) startIndex = (...)
let indexOf str value = indexOfFrom str value 0

The alternative is to define the functionality as members of a type - then you can use both overloading and F# optional arguments, but you'd have to access them using full 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 of the options is the best one?

  • If you're designing functional API (e.g. domain-specific language), then your option with two separate functions is probably the best choice.

  • If you're aiming to design a nice F# API, then I think your option (multiple functions) or optional parameters are quite reasonable. Functions are used quite heavily in Deedle and F# Charting relies on optional arguments.

  • The benefit of using overloading is that the library will be also nicely usable from C#. So, if you're thinking of calling the library from C#, this is pretty much the only option.

like image 167
Tomas Petricek Avatar answered Oct 31 '22 17:10

Tomas Petricek


I think option 1 (with curried function) would be simplest. Curried functions are pretty common in functional programming.

In options 2 or 3 you'll still have to pass additional parameter to the function for the search from the beginning

Options 4 or 5 require additional overhead to create type. It's kind of 'overkill' for this simple task

like image 6
Petr Avatar answered Oct 31 '22 16:10

Petr