[Swift] best way to support 'keyword' args, symbolic values, show values as literals?
[Swift] best way to support 'keyword' args, symbolic values, show values as literals?
- Subject: [Swift] best way to support 'keyword' args, symbolic values, show values as literals?
- From: has <email@hidden>
- Date: Mon, 15 Jun 2015 18:17:15 +0100
Hi folks,
Some of the old uns here might remember that many years ago I wrote a
nice little library named appscript which allowed you to control
"AppleScriptable" applications from Python/Ruby/ObjC (and, unlike the
alternatives, actually didn't suck), allowing you to write code like
this (Python):
app('TextEdit').documents['Read Me'].paragraphs[1].get()
and this (ObjC):
TEApplication *textedit = [TEApplication applicationWithName:
@"TextEdit"];
TEReference *ref = [[textedit.documents byName:
@"ReadMe"].paragraphs at: 1];
NSString *result = [[ref get] send];
instead of this (AppleScript):
tell application "TextEdit"
get paragraph 1 of document "ReadMe"
end tell
Thanks to Apple actioning Radar tickets 19169736
<http://openradar.appspot.com/19169736>, 19169791
<http://openradar.appspot.com/19169791>, and 18944184
<http://openradar.appspot.com/18944184>, there are now modern, fully
supported NSAppleEventDescriptor APIs arriving in 10.11 that will allow
third-party code to do all this stuff without having to go through
ancient legacy or deprecated Carbon APIs any more.
So I'm starting to work on an Apple event bridge for Swift 2.0, built on
top of a cleaned-up fork[1] of my old Appscript.framework. So far it's
able to construct basic object specifiers, e.g.:
TEApplication(name: "TextEdit").documents[1].name
and symbolic values (AEDescs of typeType and typeEnumerated, aka 'class'
and 'constant' names in AS):
TESymbol.name
Complex specifiers and commands are not yet up and running (once they
are, I'll push the code) and this being my first substantial foray into
Swift, I could do with advice and suggestions on how best to present the
API. Here's 3 questions to start:
...
1. AppleScript, Apple events, etc. use optional keyword-based
parameters, not ordered parameters as in ObjC and Swift. For ObjC I just
used chained method calls to build up and send complex events, e.g:
tell application "TextEdit" to make new document ¬
with properties {text:"Hello World\n"}
translates to objc-appscript as:
[[[[textedit make] new_: [TEConstant document]]
withProperties: @{TEConstant.text: @"Hello World"}] send]
which works fine but is pretty ugly compared to the Python equivalent:
textedit.make(new=k.document, with_properties={k.text: "Hello
World"}, timeout=30)
Translating the ObjC API directly to Swift would result in code like this:
textedit.make.new(TEConstant.document).withProperties(
[TEConstant.text: "Hello World"]).send(timeout: 30)
which isn't particularly intuitive or readable, so I'm looking for other
ways to do it, preferably that allow the entire Apple event to be built
and sent in a single method call.
If I understand Swift, there are a couple possible approaches that would
let me get much closer to the Python/Ruby syntax: optional arguments and
dicts. Since optional args are only omittable from the end of the args
list, that'd mean a syntax something like this:
textedit.make(new: TESymbol.document, at: nil, withData: nil,
withProperties: [TESymbol.text: "Hello World"],
eventAttributes: [keyTimeoutAttr: 30])
which isn't the most beautiful (and starts to look unpleasant when
constructing application commands that have really long verbose argument
lists), but is possibly the most "Swiftian" way to do it. Another option
would be to do what I did in Ruby, which was for each command to take 0,
1, or 2 of the following arguments: a direct parameter, and a dictionary
containing all keyword parameters and/or event attributes:
textedit.make(args: [
TESymbol.new: TESymbol.document,
TESymbol.withProperties: [TESymbol.text: "Hello World"],
TESymbol.timeout: 30])
or:
textedit.make(args: [
"new": TESymbol.document,
"withProperties": [TESymbol.text: "Hello World"],
"timeout": 30])
depending on whether one prefers parameter names to be given as symbols
or strings.
If anyone has any thoughts or other ideas, I'd very much like to hear them.
...
2. Does anyone have any particular thoughts on how best to present
symbolic values? In Python I just had an dynamic object (`k`) that
pretended to be an infinite namespace so one could write `k.document`,
`k.text`, etc. and the application would figure out what AEDesc code
('docu', 'ctxt') that actually represented when packing it into the AE.
For now I'm emulating the objc-appscript approach, which is to have a
statically generated class (e.g. `TESymbol`) with a whole bunch of class
methods on it that return the corresponding instances (e.g.
`TESymbol.document`).
Right now I'm defining these on the generated Swift class as static
vars, but I dunno if there'd be a better way of presenting these, e.g.
as lazy vars on the module itself (`TEDocument`, `TEText`, etc). (Bear
in mind that these symbols are defined all over the place - some in the
app's terminology, some in AppleScript's - so some looseness/flexibility
is required vis-a-vis types.)
I'm also wondering if it'd be better to drop the ObjC-style name
prefixes and just use the module's own namespace name to disambiguate as
needed, e.g. `TEGlue.document`, `TEGlue.text`, or even (e.g.)
`kTEDocument`, `kTEText`?
Again, any thoughts/preferences? (Bear in mind that a Swift program
might use several different apps, so each app's terms really should stay
in their own namespace to avoid conflicts/confusion.)
...
3. Is there any easy way to get the _literal_ representation of a
standard Swift type? As with py-appscript, objc-appscript, etc. the goal
is to enable a user to print an object specifier and be able to
copy-and-paste that straight into another script - i.e. `-description`
should always return a string that represents valid Swift code.
Python provides a _very_ convenient `repr()` function and "{!r}"
interpolation that does this automatically; however, for ObjC I had to
write my own code to describe NSNumbers, NSStrings, NSArrays, etc. using
literal ObjC syntax - @1, @"...", @[...]", etc. - since those classes'
own -description methods would return "human-readable" rather than
literal representations. Is there a Python-like way to do it in Swift,
or do I have to write another formatter from scratch?
...
Feel free to rummage around the Python/Ruby/ObjC documentation on the
appscript site if it helps to inspire, and all advice, suggestions,
criticisms, etc. will be much appreciated.
Many thanks,
has
--
[1] https://bitbucket.org/hhas/appleeventbridge
[2] http://appscript.sourceforge.net
_______________________________________________
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