Skip to content

Instantly share code, notes, and snippets.

@marcpalmer
Last active January 2, 2019 21:11
Show Gist options
  • Save marcpalmer/423610cb91237dfb4455431165a21f63 to your computer and use it in GitHub Desktop.
Save marcpalmer/423610cb91237dfb4455431165a21f63 to your computer and use it in GitHub Desktop.
Trying to find a way to make typesafe builders constrained on a Self type
//
// Paste this code into a playground to see the compiler error and have a shot at a solution.
// All suggestions gratefully received. This is really an exercise in seeing if there is a way to lock down
// intermediary command "binding" types to only those that relate to the same kind of Robot as the CommandBuilder expects.
//
// There is a compiler error when we try to add the actual binding to the builder:
//
// Collaborator with Constrained Self Conformance.playground:63:21: error: cannot convert
// value of type 'CommandBinding<MetalMickey, DanceCommand>' to expected argument type 'CommandBinding<_, _>'
// builder.add(dance)
// ^~~~~
import UIKit
protocol Command {
associatedtype InputType
static var name: String { get }
}
protocol Robot {
static var description: String { get }
}
class CommandBuilder<R> where R: Robot & Programmable {
var commandMappings: [String:Any] = [:]
func add<C>(_ commandBinding: CommandBinding<R, C>) {
commandMappings[commandBinding.command.name] = commandBinding
}
}
/// Define a convention that programmable robots implement to build their command
/// list when passed a builder created to only contain commands that apply to their own type
protocol Programmable {
static func defineCommands<R>(_ builder: CommandBuilder<R>) where R: Robot
}
class CommandBinding<R, C> where R: Robot, C: Command {
let robot: R.Type
let command: C.Type
init(robot: R.Type, command: C.Type) {
self.robot = robot
self.command = command
}
func displayText() -> String {
return "\(command.name) on \(robot.description)"
}
}
/// Provide a function that allows the programmable robots to construct a generic builder
/// specific to their own Self type
extension Programmable where Self: Robot {
// Result is Any for simplicity of example
static func collectCommands() -> [Any] {
let builder = CommandBuilder<Self>()
// Get Self (a Programmable) to define the commands
defineCommands(builder)
return Array(builder.commandMappings.values)
}
}
// Using the API
class DanceCommand: Command {
typealias InputType = String
static var name: String = "dance"
}
class MetalMickey: Robot, Programmable {
static var description: String = "Metal Mickey"
static let dance = CommandBinding(robot: MetalMickey.self, command: DanceCommand.self)
static func defineCommands<R>(_ builder: CommandBuilder<R>) where R: Robot & Programmable {
/// !!! Here's the problem
builder.add(dance)
}
}
/// Our app might iterate over a lot of these types
let allRobotTypes: [(Robot & Programmable).Type] = [
MetalMickey.self
]
func buildCommandsFor(_ robot: Robot.Type) {
if let programmableRobot = robot as? (Robot & Programmable).Type {
let commands = programmableRobot.collectCommands()
print("Commands: \(commands)")
}
}
for type in allRobotTypes {
buildCommandsFor(type)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment