In JavaScript/TypeScript we have this pattern that is often used in libraries where you have one optional parameter as the last function argument which defaults to some default configuration. It looks like the following:
const smth = someLibrary("requiredParameter", {
someOptionalProperty: true,
// ...other options available, but not required to be provided here.
});
On the library side it would look something like this:
export const someLibrary = (requiredParameter: string, options?: Options) => {
const opts = { ...defaultOptions, ...options };
// Here `opts` will have the default properties merged with the ones that you provided.
}
Now, in Swift, from what my 2-3 year experience has told me, if you want to do something similar you have to make a struct with all the configuration parameters and then mutate the defaults after instantiating the struct. This would look something like the following:
struct Options {
var option1: Double = 2.0
var option2: Int = 1
// ...etc
}
public func someLibrary(_ requiredParameter: String, options: Options?) {
// ...
}
let opts = Options()
opts.someParameter = "Overriding the values here"
let result = someLibrary("requiredParameter", options: )
The problem
There are a couple of problems with the Swift implementation that JavaScript does very well:
- Default options override, but just the fields that were changed - I haven't been able to replicate this in swift with a solution that is at least close in elegancy.
- Partial override of the options - the approach in Swift that I was using is to have an initializer for the struct with all optional parameters, but the initializers become huge for big configurations.
The Question
Now the question is - how close can I get to the JavaScript version using Swift?
Maybe there's a pattern or a function that I'm missing that would do that for me?
The goal for me is to not have huge boilerplate to make my library customisable.
CodePudding user response:
Here a sample example :
struct Options {
// option values are saved in private dictionary of Any
private var options = [String:Any]()
init(withOptions options: [String:Any]) {
self.options = options
}
// Get int option
subscript(intOption: String) -> Int {
return options[intOption] as? Int ?? 0
}
// Get string option
subscript(stringOption: String) -> String {
return options[stringOption] as? String ?? ""
}
// Get float option
subscript(floatOption: String) -> Float {
return options[floatOption] as? Float ?? 0.0
}
// generic option but return an optional
subscript<T>(withName: String) -> T? {
return options[withName] as? T
}
}
let options: Options = Options(withOptions: ["option1": 1,
"option2": "string 2",
"option3": Float(3.0), // set type if you want correct value
"option4": 4.0])
let o1: Int = options["option1"]
let o2: String = options["option2"]
let o3: Float = options["option3"]
let o4: Double? = options["option4"]
This is a base that may help you, Here the subscript are an nice way go get values.
EDIT : added non typed access EDIT 2 : added Float for type accuracy
CodePudding user response:
One way to solve this is to let the caller configure the options in a closure, for this to work smoothly it is better if Options
is changed to be a class
class Options {
var option1: Double = 2.0
var option2: Int = 1
var option3: String = "abc"
}
Then we can change the signature of the library function to
func someLibrary(_ requiredParameter: String,
options: ((Options) -> Void)? = nil)
and then in the body of the function call the closure
let libraryOptions = Options() //Holds all default values
if let options = options {
options(libraryOptions)
}
// use libraryOptions in code...
This can then be used in the client like this
someLibrary("requiredParameter") { options in
options.option1 = 4.2
}
Old answer
One solution is to create an init for Options
that takes default values
struct Options {
var option1: Double
var option2: Int
var option3: String
init(option1: Double = 2.0, option2: Int = 1, option3: String = "abc") {
self.option1 = option1
self.option2 = option2
self.option3 = option3
}
}
Then you get a use case that is similar to your JS example
let options = Options(option1: 4.2)
someLibrary("requiredParameter", options: options)