Reputation: 960
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
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
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