The instantiation of objects from classes or types is an activity which affects the behaviour of other parts of the analysis system. Consider the creation and usage of specialisations; upon specialising a function the following steps are performed:
In the simplest case, where only a single instance is constructed for each type, the number of possible signatures is constrained according to the number of types defined in the program. However, where multiple instances can be defined for any given type, such guarantees are less certain.
Consider the case of a recursive function f(x) which calls itself:
def f(x):
if len(x) == 1:
return x
else:
y = []
y.append(x[0] + x[1])
for i in x[2:]:
y.append(i)
return f(y)
Here, a new list is created which contains elements from the original list supplied as an argument to the function. In the simplest case, we can say that the recursive call within the function is a call to a specialisation that already exists, and we can even take steps to curtail re-evaluation of the specialisation and avoid infinite recursion in the analysis of this code. However, should we decide to permit each "instantiation site" to create a new object from a particular type - in this case, a new list object for the list created by the function - then unless we have a means to limit this instantiation, the result will be the following:
In other words, the program's "natural" constraints no longer apply and we are effectively executing certain aspects of the program itself.
The above problem with recursive functions can be reduced to a question of equivalence. If each new list cannot be considered equivalent to previous lists, and thus cannot make each signature equivalent to those produced before it, then the process of analysis will continue to produce new specialisations. However, to regard instances as equivalent is not necessarily a trivial matter; consider the following instances:
i = [[[1]]]
j = [[["a"]]]
Whilst both instances are superficially similar at the highest level (lists) and one level down (lists of lists), one might imagine that to fully test the equivalence of instances, one would need to test equivalence to an arbitrary number of levels. Moreover, with recursive structures the number of levels would tend to infinity. Consider this example:
def f(x):
if not isinstance(x, list):
return g(x)
else:
return f(x[0])
def g(x):
return x + x
f([[[1]]])
f([[["a"]]])
Here, a superficial consideration of all lists being equal would result in the function g being specialised to accept a list as argument even though the code is clearly not written to entertain such a possibility.