Getting every foo whose name is bar
- Published
- 2013-02-04
- Tagged
There’s a particular coding idiom that’s prevalent in Applescript:
1 | set subSet to every item whose name is "bar" |
Usually I have to surround various bits of the command with parentheses so Applescript knows what the hell I mean, but you get the overall pattern. I can specify a number of conditions, and AppleScript will fetch only the records that match said conditions. In much the same way as you’d put filters on a SQL query, you want to use filters because it’s generally faster and easier than trying to do it through “proper” AppleScript.
rb-appscript
has a similar setup:
1 | include Appscript |
2 | subSet = items[its.name.eq 'bar'] |
Although this gets messy when you have two or three conditions:
1 | subSet = items[its.name.eq('bar').and(its.done.eq(true).and(its.size.gt(3)))] |
Switching to MacRuby recently, one of the things I missed was this “filter” functionality. I couldn’t find any examples anywhere of how to select every item whose name was “bar” without making a somewhat time-consuming select
loop:
1 | subSet = items.get.select{ |i| i.name == 'bar' } |
Thankfully, there is a way to do it in MacRuby, it’s just a bit more complex.
The first step is to realise that MacRuby has overloaded the Object#methods
method to let you access Objective-C-style methods (more info here). Using the call methods(true,true)
on an array got me a whole heap of interesting methods you can now use on arrays, including the filterUsingPredicate
method. This method takes a NSPredicate
and uses it to filter your list of arrays - which can be considerably faster than the horrid select
loop suggested above.
You make a NSPredicate
in the following manner:
1 | n = NSPredicate.predicateWithFormat %(name = "bar") |
That’s a string at the end, I’m just using alternate quotes to look cool and avoid having to escape my double-quotes. You can find out all about predicates here.
Now all I do is feed in my predicate to the array:
1 | subSet = items.filterUsingPredicate(n) |
How much faster is this than running a select
loop? I tried benchmarking it on my OmniFocus database:
1 | !/usr/bin/env macruby |
2 | require 'benchmark' |
3 | |
4 | framework 'ScriptingBridge' |
5 | |
6 | of = SBApplication.applicationWithBundleIdentifier('com.omnigroup.omnifocus').defaultDocument |
7 | |
8 | Benchmark.bm(20) do |x| |
9 | x.report('Predicate') do |
10 | n = NSPredicate.predicateWithFormat("completed = TRUE") |
11 | of.flattenedTasks.filterUsingPredicate(n) |
12 | end
|
13 | |
14 | x.report('Select') do |
15 | of.flattenedTasks.select{ |t| t.completed } |
16 | end
|
17 | end
|
The result:
1 | user system total real |
2 | Predicate 0.000000 0.000000 0.000000 ( 0.002507) |
3 | Select 0.130000 0.070000 0.200000 ( 6.323786) |
Using select, it takes six seconds just to get the data. With predicate, you don’t even notice. Quite an improvement.