Reputation: 145
I believe I have some misunderstanding of how generics work. I have the protocol:
protocol CommandProtocol {
func execute<T>() -> T
func unExecute<T>() -> T
}
And a class that conforms to it:
class CalculatorCommand: CommandProtocol {
...
func execute<String>() -> String {
return calculator.performOperation(operator: `operator`, with: operand) as! String
}
func unExecute<Double>() -> Double {
return calculator.performOperation(operator: undo(operator: `operator`), with: operand) as! Double
}
...
}
The calculator.performOperation() method actually returns Double, but here I just try to play with generics so I replace return type from Double to String.
After that, I have a class which invokes these methods:
class Sender {
...
// MARK: - Public methods
func undo() -> Double {
if current > 0 {
current -= 1
let command = commands[current]
return command.unExecute()
}
return 0
}
func redo() -> Double? {
if current < commands.count {
let command = commands[current]
current += 1
let value: Double = command.execute()
print(type(of: value))
return command.execute()
}
return nil
}
...
}
In the undo() method everything works as expected (one thing that I did not understand fully is how Swift really knows whether the unExecute value will return Double or not, or compiler infers it based on the undo() return type?)
But in the redo() method, I am calling the execute() method which returns String, but the method expects Double, so I thought that my program would crash, but not, it works totally fine as if execute() method returns Double. Please, could someone explain to me what exactly happens under the cover of this code? Thank you in advance.
Upvotes: 1
Views: 97
Reputation: 299663
You are correct that you misunderstand generics. First, let's look at this protocol:
protocol CommandProtocol {
func execute<T>() -> T
func unExecute<T>() -> T
}
This says "no matter what type the caller requests, this function will return that type." That's impossible to successfully implement (by "successfully" I mean "correctly returns a value in all cases without crashing"). According this protocol, I'm allowed to write the following code:
func run(command: CommandProtocol) -> MyCustomType {
let result: MyCustomType = command.execute()
return result
}
There's no way to write an execute
that will actually do that, no matter what MyCustomType
is.
Your confusion is compounded by a subtle syntax mistake:
func execute<String>() -> String {
This does not mean "T = String," which is what I think you expect it to mean. It creates a type variable called String
(that has nothing to do with Swift's String type), and it promises to return it. when you later write as! String
, that means "if this values isn't compatible with the type requested (not "a string" but whatever was requested by the caller), then crash.
The tool that behaves closer to what you want here is an associated type. You meant to write this:
protocol CommandProtocol {
associatedType T
func execute() -> T
func unExecute() -> T
}
But this almost certainly won't do what you want. For example, with that, it's impossible to have an array of commands.
Instead what you probably want is a struct:
struct Command {
let execute: () -> Void
let undo: () -> Void
}
You then make Commands by passing closures that do what you want:
let command = Command(execute: { self.value += 1 },
undo: { self.value -= 1 })
Alternately, since this is a calculator, you could do it this way:
struct Command {
let execute: (Double) -> Double
let undo: (Double) -> Double
}
let command = Command(execute: { $0 + 1 }, undo: { $0 - 1 })
Then your caller would look like:
value = command.execute(value)
value = command.undo(value)
Upvotes: 0
Reputation:
You think this returns a Swift.Double, but no. This code is no different than using T
instead of Double
. Swift does not require the names of generic placeholders to match what you put in a protocol.
func unExecute<Double>() -> Double {
return calculator.performOperation(operator: undo(operator: `operator`), with: operand) as! Double
}
You're not actually looking for generic methods. You want this, instead.
protocol CommandProtocol {
associatedtype ExecuteValue
associatedtype UnExecuteValue
func execute() -> ExecuteValue
func unExecute() -> UnExecuteValue
}
Upvotes: 0