Type inference problem
Suppose you have an expression in our Language of expressions which is internally translated to a graph of parallel assignment statements. Let \(V\) be a set of variables which appear in all those statements. A mapping \(V \rightarrow I \) maps variables \((v_1 \in V, v_2 \in V,\dots) \) to types of values bound to those variables \((t_1 \in I, t_2 \in I,\dots) \) in some point of time during evaluation of our expression. Suppose our execution graph has \(n\) vertices (statements), then let \([V \rightarrow I]^n\) be a mapping from variables to types before execution of all \(n\) statements in the graph. A mapping \([V \rightarrow I]^n\) is called safe if for types \((t'_{1_m}, t'_{2_m}, \dots)\) of values bound to variables \((v_1, v_2, \dots)\) during any possible control flow graph execution before execution of any statement \(m\) holds the following: \(t'_{1_m} \le t_{1_m}\), \(t'_{2_m} \le t_{2_m}\) etc. For example a mapping \([v_1 \rightarrow T, v_2 \rightarrow T, \dots]^n\) is always safe. The problem of type inference is to find a mapping \([V \rightarrow I]^n\) which is safe and as narrow as possible.
When your type system and database of known functions are created, you can
start to infer types of expressions. Let's add a function infer-types
to
our tie-example
package:
(defun infer-types (expression)
"Infer types in the expression using our type system and function
database."
(let ((graph (tie:parse-expr expression)))
(values
(tie:infer-types *fndb* *my-system* graph)
graph (tie:result-variable graph))))
(export 'infer-types)
Here we give it a try:
CL-USER> (tie-example:infer-types '(1+ x))
#(#((#:RES483 . #<TYPE-INFERENCE-ENGINE:TYPE-NODE REAL {100E6E3B73}>)
(X . #<TYPE-INFERENCE-ENGINE:TYPE-NODE REAL {100E6E3B73}>))
#((#:RES483 . #<TYPE-INFERENCE-ENGINE:TYPE-NODE T {100E6E3BD3}>)
(X . #<TYPE-INFERENCE-ENGINE:TYPE-NODE REAL {100E6E3B73}>)))
((TYPE-INFERENCE-ENGINE:FLAT-CONTROL-NODE (1)(1)(#<TYPE-INFERENCE-ENGINE:STATEMENT X ← (INITIALIZE )>
#<TYPE-INFERENCE-ENGINE:STATEMENT RES483 ← (INITIALIZE )>))
(TYPE-INFERENCE-ENGINE:FLAT-CONTROL-NODE (0)(0)(#<TYPE-INFERENCE-ENGINE:STATEMENT RES483 ← (1+ X)>)))
#:RES483
Here the first returned value is our \([V \rightarrow I]^2\) mapping which
corresponds to two parallel assignment statements which are returned as the
second value. The third value is a variable which will hold the result of
(1+ x)
. The first element of the first returned value is of interest
here. It is a mapping \(V \rightarrow I\) which corresponds to the fully
evaluated expression (1+ x)
. As you can see it takes a value of type
REAL
and returns a REAL
. Here is another example:
#(#((X . #<TYPE-INFERENCE-ENGINE:TYPE-NODE REAL {100E6E3B73}>)
(#:RES497 . #<TYPE-INFERENCE-ENGINE:TYPE-NODE INTEGER {100E6E3B43}>)
(#:VAR498 . #<TYPE-INFERENCE-ENGINE:TYPE-NODE INTEGER {100E6E3B43}>))
#((X . #<TYPE-INFERENCE-ENGINE:TYPE-NODE REAL {100E6E3B73}>)
(#:RES497 . #<TYPE-INFERENCE-ENGINE:TYPE-NODE T {100E6E3BD3}>)
(#:VAR498 . #<TYPE-INFERENCE-ENGINE:TYPE-NODE T {100E6E3BD3}>))
#((X . #<TYPE-INFERENCE-ENGINE:TYPE-NODE REAL {100E6E3B73}>)
(#:RES497 . #<TYPE-INFERENCE-ENGINE:TYPE-NODE T {100E6E3BD3}>)
(#:VAR498 . #<TYPE-INFERENCE-ENGINE:TYPE-NODE INTEGER {100E6E3B43}>)))
((TYPE-INFERENCE-ENGINE:FLAT-CONTROL-NODE (2)(1)(#<TYPE-INFERENCE-ENGINE:STATEMENT X ← (INITIALIZE )>
#<TYPE-INFERENCE-ENGINE:STATEMENT RES497 ← (INITIALIZE )>
#<TYPE-INFERENCE-ENGINE:STATEMENT VAR498 ← (INITIALIZE )>))
(TYPE-INFERENCE-ENGINE:FLAT-CONTROL-NODE (0)(2)(#<TYPE-INFERENCE-ENGINE:STATEMENT VAR498 ← (FLOOR X)>))
(TYPE-INFERENCE-ENGINE:FLAT-CONTROL-NODE (1)(0)(#<TYPE-INFERENCE-ENGINE:STATEMENT RES497 ← (1+ #:VAR498)>)))
#:RES497
The first element of the first returned value says that an expression (1+
(floor x))
takes a REAL
argument x
and returns an INTEGER
.