FRocha
FRocha

Reputation: 960

Dealing with optional parameters in a method

Suppose I have a method "m" inside a class that has several optional parameters, as follows:

[<AbstractClass;Sealed>]
type API() =
    static member m(?x1,?x2,?x3,?x4,?x5,?x6,?x7,?x8,?x9) = //performs operations and returns a string

Arguments x1 to x4 could be strings or a string*string list tuple. To handle this, I imagined some options:

1) A DU would be exposed to the end user

Pros: the code implementation would be extremely easy

Cons: it’s not a good experience for the user to wrap a string inside a DU constructor every time, for example

2) Define x1 to x4 as obj and handle the possible cases internally, raising an exception in case a wrong type is provided

Pros: the code implementation would be extremely easy, the user doesn’t have to use DU constructors

Cons: not type safe (can raise exceptions), the user could not extract info from the method signature

3) Method overloading

Pros: type safe, the user doesn’t have to use DU constructors

Cons: implementation and use (see question below)

Given this scenario, my questions are:

1) Is there a fourth option here that I’m not seeing?

2) Regarding method 3:

a) When I tried using it by omitting x1 parameter (it’s supposed to be optional, after all), the error “unique overload could not be determined” showed. How do I handle it?

b) In this example, I have 8 possibilities. Do I have to repeat almost the same code 8 times to implement the overload?

Upvotes: 1

Views: 98

Answers (2)

Asti
Asti

Reputation: 12667

Another option you can consider to is to use named arguments.

[<AbstractClass;Sealed>]
type API() =
    static member m() = ()
    static member m(?localuris, ?remoteuris: string list, ?acl) = ()
    static member m(?localhost: string,?localport,?localscheme) = ()

You can disambiguate with names, and implement the minimum number of overloads. You'll have to implement a method m() so that the compiler can pick the definitive overload with no arguments provided. I sometimes use this as the FP equivalent of a record with optional fields.

API.m()
API.m(localhost = "127.0.0.1")
API.m(remoteuris = [ "192.168.1.10" ; "192.168.1.11" ])

Upvotes: 2

Tomas Petricek
Tomas Petricek

Reputation: 243061

Regarding method 3: I don't think there is an easy way to make this work for all possible combinations of parameters and types. You would probably need all possible combinations of members, which leads to a combinatorial explosion. I think the compiler would only be able to resolve those if you were not using optional parameters. So, you might need something like:

static member m(x1:string) = ..
static member m(x1:string, x2:string) = ..
static member m(x1:string, x2:(string*string) list) = ..
static member m(x1:(string*string) list) = ..

Your method 2, with obj as arguments would work, but as you say - it has the obvious drawback of not being type safe.

Regarding alternative options: I would perhaps consider using the builder pattern here:

type APIBuilder(...) = 
  member __.x1(v:string) = APIBuilder(...)
  member __.x1(v:(string*string) list) = APIBuilder(...)
  member __.x2(v:string) = APIBuilder(...)
  member __.x2(v:(string*string) list) = APIBuilder(...)

type API() =
  static member m() = APIBuilder()

The APIBuilder class would need to take all those parameters as the parameters of the constructor and expose some Build method to actually produce the resulting object. You would be able to write:

API.m().x3("Hi there").x2(["a","Hi there"])

This is not as compact as the optional parameter approach, but it is pretty readable. It also nicely deals with the overloading, because you only need two overloads of the methods that set individual parameters.

Upvotes: 2

Related Questions