Alpha Technical Note #004 |
How to use predicates with Alpha |
Introduction
Predicates are sets of logical conditions which are to be tested against
some attributes. They act as a logical operator and they return a boolean
value (1 or 0) to indicate whether the tested values meet the conditions
or not.
A simple example of a predicate is expressed loosely by a phrase
like "first name begins with A and weight is less than 175 pounds". If
you apply this predicate to Alice who weighs 170 pounds, the predicate will
return 1. Applied to Andrew who weighs 195.5 pounds, the predicate will
return 0 (same for Bob because his first name does not begin with an A!).
There are two kinds of predicates:
- comparison predicates are single conditions that compare two
expressions using an operator. The expressions are referred to as the left
hand side and the right hand side of the predicate with the operator
standing in
the middle.
- compound predicates assemble several comparison predicates (or other
compound predicates) using logical operations like AND and OR.
The previous example was a compound predicate made of two
comparison predicates: the predicate "first name begins with A" and the
predicate "weight is less than 175".
Building a predicate
The predicates in Alpha are identified by a unique token. There are two
methods, explained in the next sections, to obtain a token:
- programmatically by specifying a predicate string;
- graphically by using a predicate editor view.
The predicate command
The [predicate] command has a subcommand create
which takes, as argument, a string description of the predicate and
returns a token. See [predicate create].
The description string is written according to a precise syntax.
To learn more about this syntax, see the following document:
Predicate Format String Syntax.
The example of the previous section can be described like this:
firstName BEGINSWITH 'A' AND weight < 175.
So you create the predicate with the following instruction:
set predic [predicate create "firstName BEGINSWITH 'A' AND weight < 175"]
Here firstName and weight are keys which will be used to
declare the attributes.
The purpose of this document is not to describe all the possibilities
offered by this syntax. Please read the documentation. You should
nevertheless know that it is very flexible and supports wildcards and
regular expressions.
The predicate view
In order to build predicates in a more intuitive manner, the [view] command provides a PredicateEditor view.
This section explains how to build a view corresponding to the previous
example.
The following image shows what this Predicate Editor may look like:
The first row describes a compound predicate, the next two are
comparison rows. Using the Plus button, the user can add other comparison
rows. One can also delete a row using the Minus buttons.
Here is the code used to build this interface. We first create a
dialog view with two buttons:
set root [view root Dialog -frame [list 0 0 400 140]]
hi::addOkCancelButtons $root [list OK Cancel] [list okButtonProc hi::exit]
We then add a Predicate Editor view:
set predView [view create PredicateEditor -parent $root -frame [list 13 40 370 90]]
We now configure this editor view. The most important option is the -items
option which lets you specify the row templates.
view configure $predView -items [list \
[list "firstName" [list "begins" "contains" "ends"] string] \
[list "weight" [list "<" "==" ">"] float] \
]
Its value is a list of sublists where each sublist describes a template for a
comparison row template. In the previous example:
- the first sublist has three elements: the key firstName, the
allowed comparison operators and a type set to string.
- the second sublist has three elements: the key weight, the
allowed comparison operators and a type set to float.
Note that there are many other possible comparison operators.
Next, we configure the editor to initially display only two rows
(the compound row and one comparison row):
view configure $predView -num 2
Before we display the dialog, we must define the action of the OK button.
This is implemented by the okButtonProc proc (which has been
declared above):
proc okButtonProc {token} {
global predic predView
set predic [view configure $predView -value]
set root [view parent -root $token]
view delete $root
}
Let us now display the dialog:
view show $root
Here is what we get:
Playing with the buttons, the user might configure the predicate editor
like this to reflect our initial example:
When the user presses the OK button, the okButtonProc proc
takes care of retrieving the token of the predicate via the -value option of the Predicate Editor view.
Evaluating a predicate
Now that we have obtained a predicate token (either programmatically or
graphically), we will use it to test whether some attributes meet the
conditions represented by the token. This is done with the
[predicate eval] command.
The attributes are specified as a dictionary in which the keys are
the same as those used to declare the left hand side of the comparison
predicates. In the previous example, there were two keys firstName and
weight. Let us see if Alice (who weighs 170 pounds) meets the
conditions. Recall that the predicate's token is stored in the variable
predic :
«» predicate eval $predic [list firstName Alice weight 170]
1
Now let's try with Andrew who weighs 195.5 pounds:
«» predicate eval $predic [list firstName Andrew weight 195.5]
0
Dealing with dates
The row templates used to configure the Predicate Editor view can be of
the following types: date, float, integer, list, string. This type dictates the kind of interface for the
right hand side of the comparison rows: floats, integers and strings are
set in an edit field, lists are represented by a popup menu and dates by
a date picker.
There are three important things to know about dates:
- the values returned by the date picker are floating numbers
representing the number of seconds from the reference date, which is the
first instant of 1 January 2001, GMT.
- in the attributes dictionary, you may not pass the floating number
directly because Alpha has no way to distinguish between a floating value
corresponding to a float type or a date type. You have to cast the date
values explicitely using the
date(...)
syntax. For instance, if
the date you want to test is 506170800 (which is January 15, 2017), you
must specify it as date(506170800)
in the dictionary.
- note that the dates returned by commands such as [getFileInfo] or [file mtime] are measured in the
standard POSIX style as seconds from the Unix epoch (January 1, 1970). In
order to convert them to dates relative to the 1 January 2001, you must
substract 978307200.
Let's modify the previous example to introduce a date (birth date), an
integer (number of children) and a list (grade from A to E). Here is the
code:
set predView [view create PredicateEditor -parent $root -frame [list 13 46 370 150]]
view configure $predView -flexibility 12 -items [list \
[list "firstName" [list "begins" "contains" "ends"] string] \
[list "weight" [list "<" "==" ">"] float] \
[list "birthDate" [list "<" "==" ">"] date] \
[list "children" [list "<=" ">="] integer] \
[list "grade" [list "==" "!="] list {A B C D E}] \
]
view configure $predView -num 6
Here is what it looks like (initialized with 6 rows):
Going further
Here are three links containing more information about the predicate
syntax:
As a real life example, the MacMenu package in Alpha implements a predicate
editor to add conditions on files to manipulate. One can set conditions on
the file's type, creator, size, creation date and modification date.
You can get a string representation of a predicate token with the
[predicate format]
command. For instance:
«» predicate format $predic
firstName BEGINSWITH "A" AND weight < 175
When a predicate is not needed anymore, delete it with the [predicate delete] command. For instance:
predicate delete $predic
Related Links