o Swift generics, y no can I haz u dispatch correctly?
o Swift generics, y no can I haz u dispatch correctly?
- Subject: o Swift generics, y no can I haz u dispatch correctly?
- From: has <email@hidden>
- Date: Sun, 27 Sep 2015 23:02:16 +0100
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 (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