This is an old version, view current version.

6.10 Type inference

Stan is strongly statically typed, meaning that the implementation type of an expression can be resolved at compile time.

Implementation types

The primitive implementation types for Stan are

int, real, complex, vector, row_vector,  matrix, complex_vector,
complex_row_vector, complex_matrix

Every basic declared type corresponds to a primitive type; see the primitive type table for the mapping from types to their primitive types.

Primitive Type Table. The table shows the variable declaration types of Stan and their corresponding primitive implementation type. Stan functions, operators, and probability functions have argument and result types declared in terms of primitive types plus array dimensionality.

type primitive type
int int
real real
vector vector
simplex vector
unit_vector vector
ordered vector
positive_ordered vector
row_vector row_vector
matrix matrix
cov_matrix matrix
corr_matrix matrix
cholesky_factor_cov matrix
cholesky_factor_corr matrix
complex_vector complex_vector
complex_row_vector complex_row_vector
complex_matrix complex_matrix

A full implementation type consists of a primitive implementation type and an integer array dimensionality greater than or equal to zero. These will be written to emphasize their array-like nature. For example, array [] real has an array dimensionality of 1, int an array dimensionality of 0, and array [,,] int an array dimensionality of 3. The implementation type matrix[ , , ] has a total of five dimensions and takes up to five indices, three from the array and two from the matrix.

Recall that the array dimensions come before the matrix or vector dimensions in an expression such as the following declaration of a three-dimensional array of matrices.

array[I, J, K] matrix[M, N] a;

The matrix a is indexed as a[i, j, k, m, n] with the array indices first, followed by the matrix indices, with a[i, j, k] being a matrix and a[i, j, k, m] being a row vector.

Type inference rules

Stan’s type inference rules define the implementation type of an expression based on a background set of variable declarations. The rules work bottom up from primitive literal and variable expressions to complex expressions.

6.10.1 Promotion

There are two basic promotion rules,

  1. int types may be promoted to real, and
  2. real types may be promoted to complex.

Plus, promotion is transitive, so that

  1. if type U can be promoted to type V and type V can be promoted to type T, then U can be promoted to T.

The first rule means that expressions of type int may be used anywhere an expression of type real is specified, namely in assignment or function argument passing. An integer is promoted to real by casting it in the underlying C++ code.

The remaining rules have to do with covariant typing rules, which say that a container of type U may be promoted to a container of the same shape of type T if U can be promoted to T. For vector and matrix types, this induces three rules,

  1. vector may be promoted to complex_vector,
  2. row_vector may be promoted to complex_row_vector
  3. matrix may be promoted to complex_matrix.

For array types, there’s a single rule

  1. array[...] U may be promoted to array[...] T if U can be promoted to T.

For example, this means array[,] int may be used where array [,] real or array [,] complex is required; as another example, array[] real may be used anywhere array[] complex is required.

Literals

An integer literal expression such as 42 is of type int. Real literals such as 42.0 are of type real. Imaginary literals such as -17i are of type complex. the expression 7 - 2i acts like a complex literal, but technically it combines a real literal 7 and an imaginary literal 2i through subtraction.

Variables

The type of a variable declared locally or in a previous block is determined by its declaration. The type of a loop variable is int.

There is always a unique declaration for each variable in each scope because Stan prohibits the redeclaration of an already-declared variables.2

Indexing

If x is an expression of total dimensionality greater than or equal to \(N\), then the type of expression e[i1, i2, ..., iN] is the same as that of e[i1][i2]...[iN], so it suffices to define the type of a singly-indexed function. Suppose e is an expression and i is an expression of primitive type int. Then

  • if e is an expression of type array[i1, i2, ..., iN] T and k, i1, …, iN are expressions of type int, then e[k] is an expression of type array[i2, ..., iN] T,
  • if e is an expression of type array[i] T with i and k expressions of type int, then e[k] is of type T,
  • if e has implementation type vector or row_vector, dimensionality 0, then e[i] has implementation type real,
  • if e has implementation type matrix, then e[i] has type row_vector,
  • if e has implementation type complex_vector or complex_row_vector and i is an expression of type int, then e[i] is an expression of type complex, and
  • if e has implementation type complex_matrix, and i is an expression of type int, then e[i] is an expression of type complex_row_vector.

Function application

If f is the name of a function and e1,...,eN are expressions for \(N \geq 0\), then f(e1,...,eN) is an expression whose type is determined by the return type in the function signature for f given e1 through eN. Recall that a function signature is a declaration of the argument types and the result type.

In looking up functions, binary operators like real * real are defined as operator*(real, real) in the documentation and index.

In matching a function definition, all of the promotion rules are in play (integers may be promoted to reals, reals to complex, and containers may be promoted if their types are promoted). For example, arguments of type int may be promoted to type real or complex if necessary (see the subsection on type promotion in the function application section, a real argument will be promoted to complex if necessary, a vector will be promoted to complex_vector if necessary, and so on.

In general, matrix operations return the lowest inferable type. For example, row_vector * vector returns a value of type real, which is declared in the function documentation and index as real operator*(row_vector, vector).


  1. Languages such as C++ and R allow the declaration of a variable of a given name in a narrower scope to hide (take precedence over for evaluation) a variable defined in a containing scope.↩︎