10
$\begingroup$

One aspect of a dynamically typed language is that it is possible for a variable to change its type (the "type" of the value it holds) in the course of "straight line code".

This would be code like:

var a := readline(); // a is some string
a := length(a) > 10; // a is now a boolean
if (a) {
   a := 5;            // a is now an integer
} else {
   a := "foobar";     // a is now a string again
}
print a;

I'm specifically excluding the common case where a function formal argument (or a local in a function) gets a different type on separate calls to the function because on these separate calls the actual parameter is of a different type.

I'd also like to exclude the other common case where an ordinary expected conversion from string to some numeric type is done (or vice versa), e.g.,

var a := readline(); // a is some string that represents a number, e.g., the string "25"
a := 3 * a;          // a is now represented internally as an integer

My question is: Are there any numbers (e.g., studies performed) on how often programmers of dynamic languages use variables this way? Perhaps it is language specific (e.g., cultural)? Or maybe there are particular algorithms where it is used while in others it is never used? Maybe it just doesn't happen?

$\endgroup$
14
  • 1
    $\begingroup$ Is there a particular reason you want to exclude the most common cases? $\endgroup$
    – kaya3
    Aug 14, 2023 at 18:09
  • 2
    $\begingroup$ I think you're asking the wrong question. You should ask how often this happens after SSA renaming. Any poly-type-ism that can be eliminated by renaming is "trivial" and won't affect the analysis of a compiler or IDE that's sophisticated enough to do partial type inference in the first place. All of your examples are eliminated by renaming except for print a; where a may be 5 or "foobar". $\endgroup$
    – benrg
    Aug 15, 2023 at 0:13
  • 3
    $\begingroup$ Do you count null/undefined as values of a separate type? Do structs with different fields have different types? How about class instances that share the same interface but not the same implementation? $\endgroup$
    – Bergi
    Aug 15, 2023 at 1:39
  • 1
    $\begingroup$ @Bergi - moving from null to some type and back - I get that. That's standard in many languages. E.g., C#/Java/anything with Optional. So no, not that. Structs with different fields/different types: That depends on the language surely - is a "struct" a bag of fields or is it something with ordered fields or fixed fields. Interfaces are obviously not it, those are polymorphic by defn, and aren't really a feature of dynamic languages. Seriously, I thought the question (and examples) were pretty clear. These these are details of particular "type systems" but look at the big picture instead $\endgroup$
    – davidbak
    Aug 15, 2023 at 2:00
  • 1
    $\begingroup$ its talked about in the TCL community as wiki.tcl-lang.org/page/shimmering not sure thaere are any stats ive seen on it though $\endgroup$
    – jk.
    Aug 15, 2023 at 8:03

1 Answer 1

9
$\begingroup$

There have been many studies that touch on these questions, but none that I know of that follow the particular constraints you set out. A lot of work on type inference for dynamically-typed languages faces this issue, and often enough in practice that it comes up as something to be dealt with, while rarely enough that it's sometimes explicitly excluded from support. Some work has looked specifically at the use of dynamically-typed features on their own.

In most cases the relevant results are on the side of something else, either an inference engine or analysis of more complex dynamic features. It doesn't seem like anyone has looked specifically at reassignment, which I do find a little surprising. I can point at some previous work, but I think to match the specific goals you have in mind you'll need to run the study yourself.

Further down I'll note some limitations, and other cases that may or may not be in scope here. Exactly what it means for a variable to hold a new type is potentially a bit tricky to nail down. For example, what constitutes a type in these languages? is it necessary that the variable be reassigned?


Xia et al. performed an empirical study on dynamic-typing behaviours in Python programs on a substantial corpus of popular real Python systems, finding that at least 6.9% of identifiers had multiple types, and another 13.4% were undetermined by their methodology, while at least 79.7% of identifiers had only a single type. They show that even assigning two different types of literal to a variable sequentially has real incidence, while still being uncommon. They present a heat map of this matrix in Figure 5, but unfortunately an "expression" category squashes together what could be many different kinds of conversion into one (and I think the table over-eagerly aligns changes of type with changes of expression kind).

Chen et al. performed another Python study and found an average of 311 instances of variables being given different types across their benchmark. I don't think this average metric they reported is particularly useful, but all but one of the systems under study had more than 50, and these were the most common dynamic-typing behaviours they measured by a large margin. Excluding the low and high outliers, between 10% and 25% of methods they examined in each system contained one or more of the traits they examined, and these would primarily be variable typing. However, most instances found did combine string with another type, noting that

It is often the case that the value of a variable is parsed from the user inputs and its type is determined by the source of the inputs (e.g., XML files, database or command line).

They do present examples from real-world systems that show other replacements of values with different types, however, including strings. An example is given from IPython where a variable holding a dictionary is assigned one of the values from that dictionary. The reporting does not have enough detail to distinguish all of the cases you want to rule out.

Furr et al. studied Ruby programs using a profile-guided tool to infer static types for unannotated code. To produce typecheckable programs, the tool required refactoring of the benchmark programs to remove precisely this issue: they performed 11 refactorings to break multi-typed variables into multiple uni-typed variables, out of 226 total refactorings across their suite (a number of these related to specific limitations of this version of the tool, like support for dynamic type tests, so the "true" proportion would be higher). They also note 12 instances of fundamentally untypeable code, notably within the optparse module that parses command-line options: while these issues are string->other conversions, the problem is that the repeated control flow gives variables multiple values for different options, so you might count these as well. However, both classes are relatively rare and do not occur in the majority of their benchmark programs.

Pradel et al. produced a tool for finding type inconsistencies in JavaScript code, and benchmarked it against a suite of published code. This system was focused on finding potential bugs, rather than observing idioms, but it did encounter several cases of mixed types within functions that seem intentional; most of these involve the "undefined" type and another, which is meaningful within JavaScript's type model but could be analysed as a single nullable type as well. Other combinations also existed, but were less common.

I believe one of the Vitek analyses of R also touches on this question, but I haven't been able to find which one, if it does exist. I have seen a presentation on the incidence of this pattern in PHP code (quite common), but can't find any archival publication of the result; it's an explicit motivator for much of the Hack work, so I expect it has been measured to matter on the Facebook codebase too.


One limitation that all of these face is that they are benchmarking against published code, which may tend not to use some of these features as much. It is likely that this overwriting is much more common in interactive use, which is inherently ephemeral and so not included in benchmarks. For example, a significant amount of R usage is entirely interactive, and maintaining a single variable for the in-progress results of where the user is up to is not uncommon, notwithstanding that the variable may hold different types internally as further analysis steps are run (and these different types may or may not be significant or known to the user). Some of this sort of interactive use may persist into non-published scripts calcified from interactive sessions.

It's also the case, though, that code where a variable has type X up to a point, and type Y thereafter, isn't necessarily resting on dynamic typing at all: without loss of generality, this can be taken as two separate variables that have the same name, one shadowing the other. It's only cases where the variable is accessed from a loop or through lexical capture, or the type changes conditionally, where the ability to mix types together really matters. This is what the refactoring from the PRuby work above rested on. The reverse can also be true: idiomatic JavaScript will declare uninitialised variables before a loop to be set inside, such as in a search — but this is strictly a change of type within JavaScript's type model, because "undefined" is its own type! Similarly, Python None is used in the same role, and is not a bottom type either. It's necessary to drill down very specifically into what is meant by type changes in order to quantify them.

I haven't touched here on another sort of "type" change: meta-mutable object values may be seen to have a different type each time one of their properties is added or removed. This could happen either as a value is built up originally (consider idiomatic JavaScript let x = {}; x.a = 1; x.b = 2;), either inline or by passing it to other code to populate; it can also happen long afterwards while the variable is still in scope somewhere. A change could also arise from modifications made to inheritance parents. Richards et al. showed that all of these sorts of change are very common in JavaScript code, and many are also possible and idiomatic in other languages on the more dynamic end of the spectrum. In Ruby, both monkeypatching existing types and eigenclass modifications are normal parts of using the language. These would be changes of a variable's type from some perspectives, and not others, and I'm not sure whether they're in scope of this question or not.


Zhifei Chen, Yanhui Li, Bihuan Chen, Wanwangying Ma, Lin Chen, and Baowen Xu. 2020. An Empirical Study on Dynamic Typing Related Practices in Python Systems. In Proceedings of the 28th International Conference on Program Comprehension (ICPC '20). Association for Computing Machinery, New York, NY, USA, 83–93. https://doi.org/10.1145/3387904.3389253

Michael Furr, Jong-hoon (David) An, and Jeffrey S. Foster. 2009. Profile-guided static typing for dynamic scripting languages. In Proceedings of the 24th ACM SIGPLAN conference on Object oriented programming systems languages and applications (OOPSLA '09). Association for Computing Machinery, New York, NY, USA, 283–300. https://doi.org/10.1145/1640089.1640110

Michael Pradel, Parker Schuh, and Koushik Sen. 2015. TypeDevil: dynamic type inconsistency analysis for JavaScript. In Proceedings of the 37th International Conference on Software Engineering - Volume 1 (ICSE '15). IEEE Press, 314–324. https://doi.org/10.5555/2818754.2818795

Gregor Richards, Sylvain Lebresne, Brian Burg, and Jan Vitek. 2010. An analysis of the dynamic behavior of JavaScript programs. In Proceedings of the 31st ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI '10). Association for Computing Machinery, New York, NY, USA, 1–12. https://doi.org/10.1145/1806596.1806598

Xia, X., He, X., Yan, Y., Xu, L., Xu, B. (2018). An Empirical Study of Dynamic Types for Python Projects. In: Bu, L., Xiong, Y. (eds) Software Analysis, Testing, and Evolution. SATE 2018. Lecture Notes in Computer Science(), vol 11293. Springer, Cham. https://doi.org/10.1007/978-3-030-04272-1_6

$\endgroup$
2
  • $\begingroup$ Very nice answer, TY! I'll look up these references and be back later ... $\endgroup$
    – davidbak
    Aug 14, 2023 at 23:39
  • $\begingroup$ Nice! It's been a long time since I've seen Diamondback Ruby referenced. $\endgroup$ Aug 15, 2023 at 14:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .