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 (Cocoa-dev@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to