Hi all,

So, as part of my ongoing quest to discover all the exciting ways in which Swift can perplex and frustrate me†, I'm trying to solve the last major blocker on my SwiftAE project (https://bitbucket.org/hhas/swiftae), which is how to use Swift's static typing to determine how NSAppleEventDescriptors are unpacked.

The ideal solution would be to define each application command as a generic method like this:

    func someCommand<T>(ARGS) -> T {

       let resultDesc = self.appData.sendAppleEvent(ARGS)

        // unpack() should introspect T and use that information to coerce
        // resultDesc to the corresponding AE type before unpacking it:

        let result = self.appData.unpack(resultDesc, returnType: T.self)

        return result as! T
    }

and define a non-generic `AppData.unpack()` method that uses introspection to pull the `returnType` argument to bits.

Alas, Swift 2's type introspection sucks absolutely monkey nuts, so AFAIK it currently isn't possible to pull apart parameterized types such as Array<String> to see how they're constructed. (Except, perhaps, by getting and parsing its string representation (euwww) and I'm not quite that desperate yet.) So, as a workaround, I've been trying to overload my `unpack` methods so that the generics system pulls apart the Swift type for me.

For atomic types (Bool, Int, String, NSURL, etc) this approach works fine: I can just use a big ol' conditional/switch to test if T.self==Bool.self, T.self==Int.self, etc. and use that to coerce and unpack AEDescs which are similarly atomic (typeBoolean, typeInteger, etc). The problem is how to do the same for Array<> and Dictionary<> types, since to unpack the corresponding typeAEList and typeAERecord AEDescs we also need to know the type(s) of the Array/Dictionary elements. For example, if the user writes:

    let result = appObj.someCommand as Array<String>

then T will be Array<String>, so the resultDesc should be coerced to typeAEList and each of its items should be coerced to typeUnicodeText before unpacking it as the specified Swift types.

After a few attempts, I finally found the following overloaded `unpack` methods †† seemed to distinguish between atomic and non-atomic Swift types:


    class AppData {

        // unpack sequence type (e.g. Array<U>)
func unpack<T: SequenceType, U where U == T.Generator.Element>(desc: Any, returnType: T.Type) -> T {
            print("unpack sequence of \(U.self)")
            return desc as! T
        }

        // unpack atomic type (e.g. String)
        func unpack<T>(desc: Any, returnType: T.Type) -> T {
            print("unpack atomic value \(T.self)")
            return desc as! T
        }
    }

This works exactly as intended when `AppData.unpack()` is called directly:

    let d = AppData()

d.unpack([1,2], returnType: [Int].self) // correctly calls `unpack<T:SequenceType…>()`


The problem is, it all falls apart again when `AppData.unpack()` is called from another generic method:

    class Commands {
        func get<T>(v:Any) -> T {
            return d.unpack(v, returnType: T.self) as T
        }
    }

    Commands().get([3,4]) as [Int] // incorrectly calls `unpack<T>()`

Dunno why, but this time the Swift compiler picks the less specific `unpack` implementation instead of the one that's got SequenceType stamped all over its signature. (Bet Dylan never had this problem...)

Mind, it still wouldn't be the end of the world if Swift only let us express constraints such as this:

        // unpack atomic type (e.g. String)
        func unpack<T where T != SequenceType>(...) -> T {
            ...
        }

but alas, once again we hit the point where Swift's type system stops pretending to be a formal set algebra and proves to be just another bunch of ad-hoc compiler kludges in the grand old tradition <ahem> of C.

Adding another `returnType` parameter and plastering the calling code with ludicrous amounts of type info makes absolutely no difference either:

    class Commands {
        func get<T>(v:Any, returnType: T.Type) -> T {
            return d.unpack(v, returnType: T.self) as T
        }
    }

let result: [Int] = Commands().get([3,4], returnType: [Int].self) as [Int] // still incorrectly calls `unpack<T>()`

Whatever it was that made `unpack(desc, returnType:)` work the first time around appears to be a one-trick pony.

So I'm kinda jiggered again by the complexities and vagaries of SwiftLang... (and this is even before I start thinking about how the smoking hell I'll ever handle sum types, as it's not uncommon for application commands to return more than one possible type of value).

So please please please, anyone got any ideas, answers, magic, etc? (Swift engineers particularly - after all, this is a great opportunity to kick its tyres and figure how to make it more robust and less ornery.)

Many thanks,

has


† This damn project is taking me far too long to complete, and it's got me cursing Swift as much as I curse AppleScript (and that is a LOT of cursing). I'd love to see Swift provide developers with a true alternative to AppleScript (ObjC and JavaScript having already tried and failed quite miserably) - plus I've got some ambitious ideas for building stuff on it myself - but in the meantime it ain't paying the bills and certainly isn't ingratiating me with Apple's bouncing new baby.

†† Just ignore the `desc: Any` argument, and concentrate on the `returnType:` argument as that's what drives everything else. In the full implementation the `desc` argument takes an NSAppleEventDescriptor instance, but these mockups omit the AEDesc unpacking logic so its only purpose here is to pass a placeholder value to keep Swift's typechecker happy. Checkout the repo if you really want to see the full thing in action.
_______________________________________________

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