Using Dynamic Member Lookup in a Superclass

As a fun project, I'm wanting to model an electronic circuit.

Components inherit from a superclass (ElectronicComponent). Each subclass (e.g. Resistor) has certain methods to return properties (e.g. resistance), but may vary by the number of outlets (leads) they have, and what they are named.

Each outlet connects to a Junction.

In my code to assemble a circuit, while I'm able to manually hook up the outlets to the junctions, I'd like to be able to use code similar to the following…

class Lead: Hashable // implementation omitted
{
	let id = UUID()
	unowned let component: ElectronicComponent
	weak var connection: Junction?

	init(component: ElectronicComponent, to connection: Junction? = nil)
	{
		self.component = component
		self.connection = connection
	}
}

@dynamicMemberLookup
class ElectronicComponent
{
	let id = UUID()
	var connections: Set<Lead> = []
	let label: String?

	init(label: String)
	{
		self.label = label
	}

	subscript<T>(dynamicMember keyPath: KeyPath<ElectronicComponent, T>) -> T
	{
		self[keyPath: keyPath]
	}

	func connect(lead: KeyPath<ElectronicComponent, Lead>, to junction: Junction)
	{
		let lead = self[keyPath: lead]
		lead.connection = junction
		connections.insert(lead)
	}
}

class Resistor: ElectronicComponent
{
	var input, output: Lead?

	let resistance: Measurement<UnitElectricResistance>

	init(_ label: String, resistance: Measurement<UnitElectricResistance>)
	{
		self.resistance = resistance
		super.init(label: label)
	}
}

let resistorA = Resistor("R1", resistance: .init(value: 100, unit: .ohms))
let junctionA = Junction(name: "A")

resistorA.connect(lead: \.outlet2, to: junctionA)

While I'm able to do this by implementing @dynamicMemberLookup in each subclass, I'd like to be able to do this in the superclass to save repeating the code.

subscript<T>(dynamicMember keyPath: KeyPath<ElectronicComponent, T>) -> T
{
	self[keyPath: keyPath]
}

Unfortunately, the compiler is not allowing me to do this as the superclass doesn't know about the subclass properties, and at the call site, the subclass isn't seen as ElectronicComponent.

I've been doing trial and error with protocol conformance and other things, but hitting walls each time.

One possibility is replacing the set of outlets with a dictionary, and using Strings instead of key paths, but would prefer not to.

Another thing I haven't tried is creating and adopting a protocol with the method implemented in there. Another considered approach is using macros in the subclasses, but I'd like to see if there is a possibility of achieving the goal using my current approach, for learning as much as anything.

Answered by DTS Engineer in 852141022

I’m gonna recommend that you ask this over in Swift Forums > Using Swift. This is a question about Swift the language, rather than anything Apple specific, and you’ll get more diverse opinions over on Swift Forums.

I’ll see you over there!

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

To clarify, the code I'd like to be able to use is line 55 in the above code block.

I’m gonna recommend that you ask this over in Swift Forums > Using Swift. This is a question about Swift the language, rather than anything Apple specific, and you’ll get more diverse opinions over on Swift Forums.

I’ll see you over there!

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Interesting question (would like to see the solution), so my 1 cent contribution. Sorry if it is worthless.

In The Swift Programming Language (Swift 5.7) :

struct Point { var x, y: Int }

@dynamicMemberLookup
struct PassthroughWrapper<Value> {
    var value: Value
    subscript<T>(dynamicMember member: KeyPath<Value, T>) -> T {
        get { return value[keyPath: member] }
    }
}

let point = Point(x: 381, y: 431)
let wrapper = PassthroughWrapper(value: point)
print(wrapper.x)

Have you tried to mimic this, by adding a value property in ElectronicComponent and calling PassthroughWrapper(value: resistorA) ?

Thanks for the suggestion. 1 cent is not valueless when someone has nothing!

This is my first venture into implementing dynamic member lookup. I'm at the stage where I believe I understand it – until I have to explain it, at which point I realise I really have no idea.

Your suggestion gave me a momentary epiphany about a possible solution, so tried to understand the code better.

At this stage, I'm thinking it still won't achieve my goal – having the compiler behave as though the method had been inherited by the subclasses, including auto-complete.

FYI Following @DTS Engineer Quinn's suggestion, I've reposted the question at swift.org, but essentially given up on the approach I was hoping for.

Having said that, your comment still feels worth exploring to see how far I can get.

@Claude31 , I thought I'd give you an update.

Played around with your suggestion a bit, and managed to get something close. This is the code I ended up with.

class Point
{
	var x, y: Int

	init(x: Int, y: Int)
	{
		self.x = x
		self.y = y
	}
}

class SubPoint: Point
{
	var z: Int

	init(x: Int, y: Int, z: Int)
	{
		self.z = z
		super.init(x: x, y: y)
	}
}

@dynamicMemberLookup
struct PassthroughWrapper<T>
{
	var value: T
	subscript(dynamicMember member: KeyPath<T, Int>) -> Int
	{ return value[keyPath: member] }
}

let point = SubPoint(x: 2, y: 3, z: 5)
let wrapper = PassthroughWrapper(value: point)
print(wrapper.z)

Or, in my case

@dynamicMemberLookup
struct PassthroughWrapper<T: ElectronicComponent>
{
	var value: T
	subscript(dynamicMember member: KeyPath<T, Lead>) -> Lead
	{ return value[keyPath: member] }
}

let resistorC = Resistor("R3", resistance: .init(value: 50, unit: .ohms))
let wrapper = PassthroughWrapper(value: resistorC)
print(wrapper[dynamicMember: \.output])
}

To elucidate, making the structure generic, things do work as I had been wanting – almost.

While everything appears to works as desired, it requires creating a wrapper for every component used. Which feels like it's negating the goal of creating connections less code.

Thanks to your suggestion, I have learned new things and new approaches. Perhaps I can repay your 1c, one day. ;-)

@reflexx Thanks for the feedback. Hope someone more expert will provide you with a $1 answer. 😉 Otherwise, don’t forget to close the thread.

Have a good day.

Using Dynamic Member Lookup in a Superclass
 
 
Q