Praat's type system

Posted by JJA on

Typing

The data that computer programs deal with comes in a variety of shapes and sizes, and will be of different types. The very common examples are strings, which represent sequences of characters, like the text you are reading, and numbers, which represent quantities. But there are more, and some computer languages allow programmers to design their own types1.

Computer languages deal with this by classifying the data they deal with according to a number of different type systems. Some languages have no type system at all, and just treat all data as sequences of bits. Other (higher level) languages have more or less strict type systems that are enforced at different times2.

The strictness of the type system will then be a result of what those rules are, when they are enforced, and whether there are any loopholes (= ways for programmers to ignore them and get away with it).

In this sense, Praat is a dynamically typed language, because there is a type system (which makes it typed), but these type rules are dynamically enforced, which means that data is type checked only when a particular statement is evaluated by the interpreter. So, while Praat makes a difference between string and numeric variables3, something like this is completely valid (but don’t do it!):

1
2
3
4
5
if 0
  string$ = 1 ; Not a string!
else
  appendInfoLine: "Safe!"
endif

Arrays

There is one more type in Praat’s current type system: although it is undocumented, and apparently not entirely implemented, Praat also supports (or “will support”) arrays.

Arrays represent an ordered sequence of values, each of which is normally subject to the same typing rules applicable in the rest of the language. It is difficult right now to say much about Praat arrays, because since they are largely experimental (and have been stuck in development for some time), it is not entirely clear what they will do, or what features will remain when they are finished.

The keen reader will have noticed that Praat already has something that fits the description above: a series of ordered values, each of which with a type. This is the case with Praat’s indexed variables. Aren’t these a different type? They certainly give rise to different error messages:

1
2
3
4
5
6
a[15] = 0
asserterror Undefined indexed variable
appendInfoLine: a[14]

asserterror Unknown variable
appendInfoLine: a_14

But while this is true it seems to me to be more an attempt at writing useful error messages, rather than evidence of them being different types. And indeed there is no evidence of an “indexed variable” type in the code4.

Compare this with the errors raised by arrays:

1
2
3
4
5
6
7
8
9
10
11
a# = zero#(5)
for i to 5
  appendInfoLine: a#[i]               ; Prints zeros
endfor

asserterror Row index out of bounds.
appendInfoLine: a#[i+1]

asserterror Found a numeric array expression
  ... instead of a numeric expression.
a = zero#(5)

The truth is that in practice, indexed variables are little more than sintactic sugar, functionally equivalent to making a number of independent but similarly named variables. Which is not to say, of course, that this isn’t welcome5.

Booleans

What about booleans? Even people with a passing understanding of computer programming will likely be familiar with the concept of a boolean variable: one that can only be true or false. Praat certainly looks like it has boolean variables:

1
2
3
4
5
6
true = 1
if true
  # Totally true!
else
  # Not so true. More like false.
endif

But once again, the value that stands in for the condition in that if statment is not a new type, it’s just a numeric variable. And any numeric variable (including those returned from functions) can take its place. Once again, this is confirmed by looking at the implementation.

But how do numbers map to boolean values? What numbers are “true”?

In Praat, any number which is not zero will be interpreted as a true value, and consecuently, only 0 will be considered false. This makes it possible to conveniently write things like this:

1
2
3
4
5
if numberOfSelected("Sound")
  appendInfoLine: "We have some sounds selected"
else
  appendInfoLine: "No sounds selected!"
endif

Some numeric functions reflect this by returning 0 when their results should be interpreted as false (I’m looking at you, index()). But this is not entirely consistent, in that some functions (like selected()) internally make an assertion on the selection (so they will crash if there is no selection, or if the selection does not match the specified class).

In computer languages, this internal conversion between non-boolean types that are interpreted as boolean gives rise to what are called “truthy” and “falsy” values: they are not strictly speaking true or false, but they seamlessly become true or false when it matters.

Stephen Colbert defining truthiness in The Colbert Report
This is a different kind of truthiness... [source]

What about strings? In many languages that use truthy values there are also rules to evaluate strings as boolean. Normally the empty string will be interpreted as false, and other strings will be true. But this is not the case in Praat: strings do not have a truthy value, so they cannot be used as conditions.

1
2
3
4
5
6
7
8
9
10
11
string$ = "something"

asserterror Found a string expression
  ... instead of a numeric expression.
if string$
  # Bad
endif

if length(string$)
  # Good
endif

Alternatively, the equality operator (which in Praat is confusingly contextual, in that == will always mean equality, but = will only sometimes mean assignment) can be used to make a string comparison, and that will evaluate to a truthy (= numeric) value.

1
2
3
4
5
6
appendInfoLine: 1 + ("a" == "a")      ; Prints 2

string$ = "something"
if string$ != ""
  # Good
endif

What about the undefined value? This is a numeric value (otherwise, we wouldn’t be able to store it in a numeric variable), and it is arguably not 0, so you could imagine that it should be true. However, in Praat this value cannot be used in conditions, and execution of a script will stop if the undefined value is compared to some other defined value.

Note, however, that there is a difference between undeclared variables (which do not exist, and are unknown to the interpreter) and undefined variables (which exist, but don’t have a value):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
asserterror Unknown variable
if mystery
  # How did we get here?
endif

mystery = undefined
asserterror The value of the 'if' condition is undefined.
if mystery
  # The mystery remains!
endif

if mystery == undefined
  appendInfoLine: "Finally!"
endif

Actually… there is one context in which an undefined value can be directly used in a condition: inline ifs.

1
2
3
4
5
6
mystery = if undefined then 1 else 0 fi
if mystery
  appendInfoLine: "Undefined is true!"
else
  appendInfoLine: "Undefined is false!"
endif

So, based on what you know so far, what do you think the last snippet will print? Can you guess before running it?


  1. Like Praat’s objects, at least as far as C++ is concerned. 

  2. Norman Ramsey regrets having to regularly write about this in StackOverflow, but what he writes is quite interesting. 

  3. This difference was not actually there from the beginning. It was introduced sometime near mid 2000, around the release of version 3.8, which has been lost to time. 

  4. Those of you so inclined can see, for example, the implementation (at the time of writing) of assignments to indexed string variables and those of indexed numeric variables, and compare them to the assignment to numeric arrays. You’ll notice that the first two are instances of Interpreter_stringExpression and Interpreter_numericExpression respectively, while the last one is an instance of Interpreter_numericArrayExpression

  5. I die a little every time I see something like variable'i'