Swift generics, circular type declarations, and segfaults, oh my!
Swift generics, circular type declarations, and segfaults, oh my!
- Subject: Swift generics, circular type declarations, and segfaults, oh my!
- From: has <email@hidden>
- Date: Thu, 03 Sep 2015 14:55:42 +0100
Hi all,
Stuck and looking for ideas here. I need to define a base class whose
methods vends instances of its subclasses (thus enabling chained method
calls; your basic query builder). Trivial to do in untyped languages
like Python (where 'type safety' is a matter of mopping up _after_ the
runtime craters):
#!/usr/bin/python
class ObjectBase:
def makeNewObject(self):
return self.__class__()
class MyObject(ObjectBase):
def foo(self):
print("foo")
print(MyObject().makeNewObject()) # result is new MyObject instance
MyObject().makeNewObject().foo() # prints "foo"
It's even somewhat doable in ObjC, as `instancetype` declares the return
type to the type system without having to state an exact type:
@implementation ObjectBase
-(instancetype)makeNewObject {
return [[self.class alloc] init];
}
@end
Although that only works when returning instances of the _same_ class;
there's no way to express that it'll return a different type that only
the subclass knows about, e.g.:
@implementation ObjectBase
typealias Other = OtherSubclass
-(instancetype.Other)makeNewObject { // wishful thinking
return [[self.class alloc] init];
}
@end
Swift though? Can't even get that far. Giant headaches all round.
Creating new instances in itself is not a problem as we can just use
`self.dynamicType` to get the subclass object and instantiate that:
class ObjectBase {
required init() { }
func makeNewObject() -> ObjectBase {
return self.dynamicType.init()
}
}
class MyObject: ObjectBase {
required init() { }
func foo() {
print("foo")
}
}
print(MyObject().makeNewObject()) // result is new MyObject instance (Yay!)
MyObject().makeNewObject().foo() // error: value of type 'ObjectBase'
has no member 'foo' (Boo!)
The problem is the base class's method's lousy type signature requires
clients to force-cast the result to the actual type before they can
refer to the returned subclass's own members:
(MyObject().makeNewObject() as! MyObject).foo() // this works
(prints "foo"), but is ugly as sin
Of course, this would make a joke of usability, so isn't even a
consideration. A kludy solution would be to override all of the base
class's methods in the subclass, allowing the correct type signatures to
be declared on its interface:
class MyObject: ObjectBase {
required init() { }
func makeNewObject() -> MyObject {
return super.makeNewObject() as! MyObject
}
func foo() {
print("foo")
}
}
But as this means replicating almost the entire base class each time,
you have to wonder what the value of subclassing over just copying and
pasting the original class each time really is. Besides, this is what
we've got generics for, so we can do nice things like tailor a reusable
class's method signatures to each specific use cases.
And which is fine… until the type parameter happens to be part of the
same inheritance tree, whereupon we tie ourselves and the compiler in
recursive knots.
First attempt, a simple generic class without any constraints:
class ObjectBase<T> {
required init() { }
func makeNewObject() -> T {
return T() // error: 'T' cannot be constructed because it has
no accessible initializers
}
}
class MyObject: ObjectBase<MyObject> {
required init() { }
func foo() {
print("foo")
}
}
Um, pretty sure it does have an initializer, but okay, let's try using
dynamicType again:
func makeNewObject() -> T {
return T.dynamicType.init()
}
Now it compiles successfully… and immediately crashes on execution.
Okay, so take that method out for now and reduce the code to the minimum
crash case:
class ObjectBase<T> {
required init() { }
}
class MyObject: ObjectBase<MyObject> {
required init() { }
}
MyObject() // crash: EXC_BAD_ACCESS on type metadata accessor for MyObject
Huh. Any completely unrelated type for T, no problem. Anything self
related, and swiftc and SourceKit DIAF at every turn, usually in a C++
stack-blowing infinite recursion of their own doing. And if it's not the
parser or compiler, it's the runtime. So before I stagger off to write a
code generator (ick) that mashes absolutely everything into single
self-contained bespoke generated class every single time, can anyone see
a way out of this recursive underbaked betaware hole I might've missed?
Thanks,
has
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden