Conference PaperPDF Available

Static Typing Where Possible, Dynamic Typing When Needed: The End of the Cold War Between Programming Languages

Authors:

Abstract

This paper argues that we should seek the golden middle way between dynamically and statically typed languages.
Intended for submission to the Revival of Dynamic Languages
Static Typing Where Possible, Dynamic Typing When Needed:
The End of the Cold War Between Programming Languages
Erik Meijer and Peter Drayton
Microsoft Corporation
Abstract
This paper argues that we should seek the golden middle way
between dynamically and statically typed languages.
1 Introduction
<NOTE to="reader"> Please note that this paper is still very
much work in progress and as such the presentation is unpol-
ished and possibly incoherent. Obviously many citations to
related and relevant work are missing. We did however do our
best to make it provocative. </NOTE>
Advocates of static typing argue that the advantages of static
typing include earlier detection of programming mistakes (e.g.
preventing adding an integer to a boolean), better documenta-
tion in the form of type signatures (e.g. incorporating number
and types of arguments when resolving names), more opportu-
nities for compiler optimizations (e.g. replacing virtual calls by
direct calls when the exact type of the receiver is known stat-
ically), increased runtime efficiency (e.g. not all values need
to carry a dynamic type), and a better design time developer
experience (e.g. knowing the type of the receiver, the IDE can
present a drop-down menu of all applicable members).
Static typing fanatics try to make us believe that “well-typed
programs cannot go wrong”. While this certainly sounds im-
pressive, it is a rather vacuous statement. Static type checking
is a compile-time abstraction of the runtime behavior of your
program, and hence it is necessarily only partially sound and
incomplete. This means that programs can still go wrong be-
cause of properties that are not tracked by the type-checker,
and that there are programs that while they cannot go wrong
cannot be type-checked. The impulse for making static typing
less partial and more complete causes type systems to become
overly complicated and exotic as witnessed by concepts such
as “phantom types” [11] and “wobbly types” [10]. This is like
trying to run a marathon with a ball and chain tied to your leg
and triumphantly shouting that you nearly made it even though
you bailed out after the first mile.
c
-Notice
Advocates of dynamically typed languages argue that static
typing is too rigid, and that the softness of dynamically lan-
guages makes them ideally suited for prototyping systems with
changing or unknown requirements, or that interact with other
systems that change unpredictably (data and application in-
tegration). Of course, dynamically typed languages are indis-
pensable for dealing with truly dynamic program behavior such
as method interception, dynamic loading, mobile code, runtime
reflection, etc.
In the mother of all papers on scripting [16], John Ousterhout
argues that statically typed systems programming languages
make code less reusable, more verbose, not more safe, and less
expressive than dynamically typed scripting languages. This
argument is parroted literally by many proponents of dynami-
cally typed scripting languages. We argue that this is a fallacy
and falls into the same category as arguing that the essence of
declarative programming is eliminating assignment. Or as John
Hughes says [8], it is a logical impossibility to make a language
more powerful by omitting features. Defending the fact that
delaying all type-checking to runtime is a good thing, is playing
ostrich tactics with the fact that errors should be caught as
early in the development process as possible.
We are interesting in building data-intensive three-tiered en-
terprise applications [14]. Perhaps surprisingly, dynamism is
probably more important for data intensive programming than
for any other area where people traditionally position dynamic
languages and scripting. Currently, the vast majority of digital
data is not fully structured, a common rule of thumb is less then
5 percent [13]. In many cases, the structure of data is only stat-
ically known up to some point, for example, a comma separated
file, a spreadsheet, an XML document, but lacks a schema that
completely describes the instances that a program is working
on. Even when the structure of data is statically known, people
often generate queries dynamically based on runtime informa-
tion, and thus the structure of the query results is statically
unknown.
Hence it should be clear that there is a big need for languages
and databases that can deal with (semi-structured) data in a
much more dynamic way then we have today. In contrast to
pure scripting languages and statically typed general purpose
languages, data intensive applications need to deal seamlessly
with several degrees of typedness.
1
2 When Programmers Say “I Need Dy-
namic/Static Typing”, They Really Mean
Instead of providing programmers with a black or white choice
between static or dynamic typing, we should instead strive for
softer type systems [4]. That is, static typing where possible,
dynamic typing when needed. Unfortunately there is a discon-
tinuity between contemporary statically typed and dynamically
typed languages as well as a huge technical and cultural gap
between the respective language communities.
The problems surrounding hybrid statically and dynamically
typed languages are largely not understood, and both camps
often use arguments that cut no ice. We argue that there is no
need to polarize the differences, and instead we should focus
on leveraging the strengths of each side.
2.1 I want type inference
Requiring explicit type declarations is usually unnecessary and
always a nuisance. For instance, no programming language
should force programmers to write pleonasms such as:
Button b = new Button();
string s = "Doh!";
We have known for at least 35 years how to have the compiler
infer the (most general) static types of local variables [3]. Not
requiring programmers to write types as dynamic languages do
is great; but not inferring the types of these variables whenever
possible is literally throwing away the baby with the bath water.
Type inference not only allows you to omit type information
when declaring a variable; type information can also be used
to determine which constructors to call when creating object
graphs. In the following example, the compiler infers from the
declaration Button b, that it should construct a new Size ob-
ject, assign the Height and Width fields to the integers 20 and
40 respectively, create a new Button instance band assign the
Size object to the Size field of b:
Button b = {
Size = { Height = 20, Width = 40 }
}
This economy of notation essentially relies on the availabil-
ity of static type information Tto seed the type-directed
e<:T e0translation of expression einto fully explicit
code e0. In other words, static type information actaully allows
programs to be more concise then their equivalent dynamically
typed counterparts.
2.2 I want contracts
Static typing provides a false sense of safety, since it can only
prove the absence of certain errors statically [17]. Hence, even
if a program does not contain any static type-errors, this does
not imply that you will not get any unwanted runtime errors.
Current type-checkers basically only track the types of expres-
sions and make sure that you do not assign a variable with a
value of an incompatible type, that the arguments to primitive
operations are of the right type (but not that the actual oper-
ation succeeds), and that the receiver of a method call can be
resolved statically (but not that the actual call succeeds).
However in general programmers want to express more ad-
vanced contracts about their code [15, 1]. For example, an
invariant that the value of one variable is always less than the
value of another, a precondition that an argument to a method
call is within a certain range, or a postcondition that the result
of a method call satisfies some condition.
The compiler should verify as much of a contract Pfor some
expression eas it can statically, say Q, signaling an error only
when it can statically prove that the invariants are violated at
some point, and optionally deferring the rest of the checking
QPto runtime via the witness f:
Q(e) e0,QP f
P(e) f(e0)
Notice the correspondence with the type-directed translation
rule above. The translation of expression eis driven by the
proof that P(e)holds.
2.3 I want (coercive) subtyping
Subtype polymorphism is another technique that facilitates
type-safe reuse that has been around for about 35 years [5].
The idea of subtype polymorphism is that anywhere a value of
some type, Dog say, is expected, you can actually pass a value
of any subtype,Poodle say, of Dog. The type Dog records
the assumptions the programmer makes about values of that
type, for instance the fact that all dogs have a Color and can
void Bark(). The fact that Poodle is a subtype of Dog is a
promise that all assumptions about Dog are also met by Poodle,
or as some people sometimes say a Poodle isa Dog.
Subtypes can extend their super types by adding new members,
for example IsShaved, and they can specialize their super type
by overriding existing members, for example by producing a
nasty yepping instead of a normal bark. Sometimes overriding
a member completely changes the behavior of that member.
The real power of inheritance, and in particular of overriding,
comes from virtual, or late-bound, calls, where the actual mem-
ber that is invoked is determined by the dynamic type of the
receiver object. For example, even though the static type of
dis dog, it will have a yepping bark since its dynamic type is
actually Poodle:
Dog d = new Poodle();
d.Bark(); // yep, yep
In some sense, objects, even without inheritance and virtual
methods, are just higher-order functions conveniently packed
together in clumps, so all advantages of using higher-order func-
tions [8] immediately carry over to objects.
Coercive subtyping is an extension of subtyping where a subtype
relation between types Sand Tinduces a witness that actually
2
coerces a value of type Sinto a value of type T[2].
e<:S e0,S<:T f
e<:T f(e0)
Notice that this is just another instance of rule for contracts
above; indeed typing is a very simple form of contracts.
Coercive subtyping is extremely powerful; driven by the type of
sub-expressions, the compiler insert coercions to bridge the gap
between inferred and required types. Perhaps the most useful
example is “auto-boxing” from value types to reference types.
In the example below, the compiler infers that the rhs of the
assignment has type int while the type of the lhs is object
and hence it inserts a boxing coercion from int to object:
object x = 4711; // new Integer(4711)
Type-directed coercions can be applied to arbitrary operations.
For example assume that we have a type int? that denotes nul-
lable (optional) integers. Based on static type information, the
compiler can automatically “lift” addition on normal integers
to addition on nullable integers:
int? x = null; int? y = 13;
int? z = x+y;
The Cωlanguage takes type-directed lifting to the extreme
by radically generalizing member access. In Cωthe .is
overloaded to lift member access over structural types such
as collections, discriminated unions, and tuples. For exam-
ple, given a collection of buttons bs we can get access all
their BackColor properties using the expression bs.BackColor.
The compiler translates this into the explicit iteration
{ foreach(b in bs) yield return b.BackColor; }.
Obviously, type-directed syntactic sugar relies static type infor-
mation. While in principle it would be possible to do automatic
lifting at runtime, this would be quite inefficient. It immedi-
ately rules out value types, since these do not carry run-time
type information in their unboxed form. On the other hand, we
sometimes do want to do truly dynamic member resolution and
dispatch. For example consider the following use of reflection
in C]to obtain the BackColor member of a button:
object b = new Button();
object c = b.GetType()
.GetField("BuckColor")
.GetValue(b);
Admittedly, this code looks pretty horrible, and even though it
seems to be “statically’ typed, it really is not. In this particular
case .GetValue("BuckColor") returns null and hence the
subsequent .GetValue(b) will throw an exception. Wouldn’t
it be much more convenient if the compiler would use the same
type-directed lifting such that we could just us ordinary member
access syntax:
object b = new Button();
object c = b.BuckColor;
Now this is not anymore type unsafe than the previous code,
but definitively more concise. Visual Basic.NET provides this
mechanism when using Option Implicit and a similar exten-
sion has been proposed for the Mono C]compiler.
There is no reason to stop at allowing late bound access to just
reflection as in the above example. There are numerous other
APIs that use a similar interpretative access/invocation protocol
such as ADO.Net, remoting, XPathNavigator, etc. For exam-
ple, the SqlDataReader in ADO.Net class exposes a method
object GetValue(int) to access the value of a particular col-
umn of the current row.
...;
SqlDataReader r =
new SqlCommand(SELECT Name, Age FROM ...", c)
.ExecuteReader();
while (r.Read()) {
...; r.GetValue(0); ...; r.GetValue(1); ...;
}
...;
Just like the reflection example, the fact that the datareader
API is “statically” typed is a red herring since the API is a
statically typed interpretative layer over an basically untyped
API. As such, it does not provide any guarantees that well-
typed programs cannot go wrong at runtime. So why not let
the compiler somehow translate r.Name into r.GetValue(0)
so that you can write a much more natural call:
...;
SqlDataReader r =
new SqlCommand("SELECT Name, Age From ...", c)
.ExecuteReader();
while (r.Read()) {
...; r.Name; ...; r.Age; ...;
}
...;
Instead of exposing all kinds of different dynamic interpretative
APIs, we should look for ways to expose them in a uniform way
via normal member access syntax.
2.4 I want Generics
The principles of abstraction and parametrization apply to types
as well as to programs. By allowing types and programs to be
parametrized by types, or even type constructors, it becomes
possible to create highly reusable libraries, while maintaining
the benefits of compile-time type checking [19]. For example,
we can define lazy streams of elements of arbitrary type Tas
follows:
interface IEnumerator<T> {
bool MoveNext(); T Current { get; }
}
In combination with type-inference, you hardly ever need to
write types explicitly. In the example below, the compiler infers
that variable xs has type IEnumerator<string>, and hence
that variable xhas type string:
3
var xs = {
yield return "Hello";
yield return "World";
};
foreach(x in xs) Console.WriteLine(x);
In languages without generics and without subtype polymor-
phism, you need to write a new sort function for any element
type, and any kind of collection. Using generics you can instead
write a single function that sorts arbitrary collections of arbi-
trary element types void Sort<T>(...). There is no need to
lose out on type safety. However, in order to sort a collection,
we should be able to compare the elements, for example using
the IComparer<T> interface:
interface IComparer<T> { int Compare(T a, T b) }
There are several ways to obtain an IComparer, first of all we
can (a) dynamically downcast an element in the collection to an
IComparer thereby potentially loosing some static type guaran-
tees, or (b) we can pass an instance of IComparer as an addi-
tional argument, thereby burden the programmer with thread-
ing this additional parameters throughout the program, or (c)
we can add a constraint to the type parameter of the sort func-
tion to constrain it to types Tthat implement IComparer<T>
only, thereby complicating the type system considerably.
In some sense solutions (a) and (c) are very similar, in both
cases the programmer somehow declares the assumption that
the type Timplements IComparer<T>. In principle the com-
piler could infer the constraint for solution (c) from the cast in
solution (a). In Haskell, member names uniquely determine the
type class in which they are defined, so you do not even need to
downcast for the compiler to infer the constraint [6]. Interest-
ingly in Haskell, the instance state and the function members
of classes are separated and the compiler passes the implemen-
tation of ICompare<T> as an additional (hidden) argument to
each function that has an ICompare<T> constraint, and also
automagically inserts instances of the actual implementations.
An alternative solution to passing additional arguments around
as in solution (b) is to use dynamically scoped variables or
implicit arguments [7, 12], something that many dynamic and
scripting languages support. In the example below, comparer is
dynamically scoped variable that will be used to pass an explicit
comparer as an implicit argument.
void Sort<T>
([implicit]IComparer<T> comparer) {...}
IComparer<T> comparer =
new CaseInsensitiveComparer();
...
myCollection.Sort();
...
Dynamically scoped variables do not require us to completely
abandon static typing. In fact, we think that dynamic scoping
becomes more understandable if the fact that a function relies
on a dynamically scoped variable is apparent in it’s type. In this
case, the static type of Sort includes the fact that it expects a
variable comparer to be in scope when Sort is being called, or
else the implicit parameter is propagated and that code itself
now recursively relies on a variable comparer to be in scope
when called. Note that this is similar to the throws clause in
Java, and in fact, dynamic variables can be implemented in a
very similar manner as exception handling.
2.5 I want (unsafe) covariance
Array covariance allows you to pass a value of type Button[]
say where a value of type Control[] is expected. Array co-
variance gracefully blends the worlds of parametric and subtype
polymorphism and this is what makes statically typed arrays
useful. There is no such thing as a free lunch, and array co-
variance comes with a price since each write operation into an
array (potentially) requires a runtime check otherwise it would
be possible to create a Button[] array that actually contains a
string:
object[] xs = new Button[]{ new Button() };
xs[0] = "Hello World";
Without covariance, we would be forced use parametric poly-
morphism everywhere we want to pass a parametric type, like
array, covariantly. This causes type parameters to spread
like a highly contagious disease. Another option is to
make the static type system more complicated by introduc-
ing variance annotations [9] when declaring a generic type
or method as in CLR generics (FooBar<+T> where T: Baz),
when using a generic type as in Java’s wild-card types
(FooBar<? extends Baz)[21].
Despite the added complexity, variance annotations and wild-
card types do not provide the full flexibility of “unsafe” covari-
ance. Before generics was introduced in Java or C], it was
not even possible to define generic collections, and hence there
was no static type-checking whatsoever. It is interesting to
note that suddenly the balance has swung to the other extreme
where people now suddenly lean over backward to make their
code statically typed.
Instead of allowing covariance by making the type-system more
complicated, we should allow covariance just like arrays by
adding a few runtime checks. Note that this is impossible in an
implementation of generics that uses erasure like in Java since
the runtime checks require the underlying element type.
2.6 I want ad-hoc relationships and prototype inher-
itance
Statically typed (object-oriented) languages such as C]and
Java force programmers to make premature commitments
about inter-entity relationships [18]. For example, at the mo-
ment that you define a class Person you have to have the divine
insight to define all possible relationships that a person can have
with any other possible object or keep type open:
class Person { ...; Dog myDog; }
The reason is that objects embed relationships as pointers
(links) within their instance, much like HTML pages embed
hyperlinks as nested <A href="..."> elements. In the rela-
tional model, much like in traditional hypertext systems, it is
4
possible to create relationships after the fact. The bad way
(BadDog below) is to embed the relationship to the parent en-
tity when defining the child entity; the good way (Dog) is to
introduce an explicit external “link table” MyDogs that relates
persons and dogs:
table Person{ ...; int PID; }
table BadDog { ...; int PID; int DID; }
table Dog { ...; int DID; }
table MyDogs { ...; int PID; int DID; }
The difficulty with the relational approach is that navigating
relationships requires a join on PID and DID. Assuming that we
have the power in the . we can simply use normal member
access and the compiler will automatically insert the witnessing
join between pand the MyDogs table:
Person p;
Collection<Dog> ds = p.MyDogs;
The link table approach is nice in the sense that it allows the
participating types to be sealed, while still allowing the illusion
of adding properties to the parent types after the fact. In cer-
tain circumstances this is still too static, and we would like to
actually add or overwrite members on a per instance basis [22].
var p = new Object();
p.Name = "John Doe";
p.Age = (){
DateTime.Today - new DateTime(19963,4,18);
};
This protype-style programming is not any more unsafe than
using a HashTable<string, object>, which as we have con-
cluded before is not any more unsafe than programming against
statically typed objects. It is important that new members show
up as regular methods when reflection over the object instance,
which requires deep execution engine support.
object c = p.GetType().GetField("Name")
.GetValue(p);
2.7 I want lazy evaluation
A common misconception is that loose typing yields a strong
glue for composing components into applications. The proto-
typical argument is that since all Unix shell programs consume
and produce streams of bytes, any two such programs can be
connected together by attaching the output of one program to
the input of the other to produce a meaningful result. Quite the
contrary, the power of the Unix shell lies in the fact that pro-
grams consume and produce lazy streams of bytes. Examples
like ls | more work because the more command lazily sucks
data produced by the ls command.
The fact that pipes use bytes as the least common denomi-
nator actually diminishes the power of the mechanism since it
is practically infeasible to introduce any additional structure,
into flat streams of bytes without support for serializing and
deserializing the more structured data that is manipulated by
the programs internally. So what you really want to glue to-
gether applications is lazy streams of structured objects; this is
the abstraction provided by lazy lists, Unix pipes, asynchronous
messaging systems, etc.
Using XML instead of byte streams as a wire-format is one step
forward, but three steps backwards. While XML allows dealing
with semi-structured data, which as we argue is what we should
strive for, this comes at an enormous expense. XML is a prime
example of retarded innovation; it makes the life of the low-
level plumbing infrastructure easier by putting the burden on
the actual users by letting them parse the data themselves by
having them write abstract syntax tree, introducing an alien
data model (Infoset) and an overly complicated and verbose
type system (XSD) neither of which blends in very well with
the paradigm that programmers use to write their actual code.
The strong similarity between the type-system of the CLR and
the JVM execution environments makes it possible to define
a common schema language, much in the style of Corba or
COM IDL, or ASN/1, that maps easily to both environments,
together with some standard (binary) encoding of transporting
values over the wire. This would be a superior solution to the
problem that XML attempts to solve.
2.8 I want higher-order functions, serialization, and
code literals
Many people believe that the ability to dynamically eval strings
as programs is what sets dynamic languages apart from static
languages. This is simply not true; any language that can dy-
namically load code in some form or another, either via DLLs
or shared libraries or dynamic class loading, has the ability to
do eval. The real question is whether your really need runtime
code generation, and if so, what is the best way to achieve this.
In many cases we think that people use eval as a poor man’s
substitute for higher-order functions. Instead of passing around
a function and call it, they pass around a string and eval it.
Often this is unnecessary, but it is always dangerous especially
if parts of the string come from an untrusted source. This is
the classical script-code injection threat.
Another common use of eval is to deserialize strings back into
(primitive) values, for example eval("1234"). This is legiti-
mate and if eval would only parse and evaluate values, this
would also be quite safe. This requires that (all) values are
expressible within in the syntax of the language.
A final use of eval that we want to mention is for partial eval-
uation, multi-stage programming, or meta programming. We
argue that in that case strings are not really the most opti-
mal structure to represent programs and it is much better to
use programs to represent programs, i.e. C++-style templates,
quasiquote/unquote as in Lisp, or code literals as in the various
multi-stage programming languages [20].
3 Conclusion
Static typing is a powerful tool to help programmers express
their assumptions about the problem they are trying to solve
and allows them to write more concise and correct code. Deal-
ing with uncertain assumptions, dynamism and (unexepected)
change is becoming increasingly important in a loosely couple
5
distributed world. Instead of hammering on the differences be-
tween dynamically and statically typed languages, we should
instead strive for a peaceful integration of static and dynamic
aspect in the same language. Static typing where possible, dy-
namic typing when needed!
4 Acknowledgments
We would like to thank Avner Aharoni, David Schach,
William Adams, Soumitra Sengupta, Alessandro Catorcini, Jim
Hugunin, Paul Vick, and Anders Hejlsberg, for interesting dis-
cussions on dynamic languages, scripting, and static typing.
References
[1] M. Barnett, R. DeLine, M. ahndrich, K. R. M. Leino,
and W. Schulte. Verification of Object-Oriented Programs
with Invariants. Journal of Object Technology, 3(6), 2004.
[2] V. Breazu-Tannen, T. Coquand, C. A. Gunter, and A. Sce-
drov. Inheritance as Implicit Coercion. In C. A. Gunter
and J. C. Mitchell, editors, Theoretical Aspects of Object-
Oriented Programming: Types, Semantics, and Language
Design, pages 197–245. The MIT Press, Cambridge, MA,
1994.
[3] L. Cardelli. Type systems. In A. B. Tucker, editor, The
Computer Science and Engineering Handbook. CRC Press,
Boca Raton, FL, 1997.
[4] R. Cartwright and M. Fagan. Soft typing. In Proceedings
PLDI’91. ACM Press, 1991.
[5] O.-J. Dahl, B. Myhrhaug, and K. Nygaard. Some Fea-
tures of the SIMULA 67 language. In Proceedings of the
second conference on Applications of simulations. IEEE
Press, 1968.
[6] C. V. Hall, K. Hammond, S. L. Peyton Jones, and P. L.
Wadler. Type Classes in Haskell. ACM Trans. Program.
Lang. Syst., 18(2):109–138, 1996.
[7] D. R. Hanson and T. A. Proebsting. Dynamic variables.
In proceedings PLDI’01, 2001.
[8] J. Hughes. Why Functional Programming Matters. Com-
puter Journal, 32(2):98–107, 1989.
[9] A. Igarashi and M. Viroli. On Variance-Based Subtyp-
ing for Parametric Types. In Proceedings ECOOP’02.
Springer-Verlag, 2002.
[10] S. P. Jones, G. Washburn, and S. Weirich. Wobbly Types:
Type Inference for Generalised Algebraic Data Types.
[11] D. Leijen and E. Meijer. Domain Specific Embedded Com-
pilers. In Proceedings of the 2nd conference on Domain-
specific languages, pages 109–122, 1999.
[12] J. R. Lewis, J. Launchbury, E. Meijer, and M. B. Shields.
Implicit Parameters: Dynamic Scoping with Static Types.
In Proceedings POPL’00, 2000.
[13] P. Lyman and H. R. Varian. How Much Information 2003.
[14] E. Meijer, W. Schulte, and G. Bierman. Programming
with Circles, Triangles and Rectangles. In Proceedings of
XML 2003, 2003.
[15] B. Meyer. Object-Oriented Software Construction (2nd
edition). Prentice-Hall, Inc., 1997.
[16] J. K. Ousterhout. Scripting: Higher-Level Programming
for the 21st Century. Computer, 31(3):23–30, 1998.
[17] B. C. Pierce. Types and Programming Languages. MIT
Press, 2002.
[18] J. Rumbaugh. Relations as Semantic Constructs in an
Object-Oriented Language. In Proceedings OOSPLA’87,
1987.
[19] D. A. Schmidt. The Structure of Typed Programming
Languages. MIT Press, 1994.
[20] T. Sheard. Accomplishments and Research Challenges in
Meta-Programming. In Proceedings of the Second Inter-
national Workshop on Semantics, Applications, and Imple-
mentation of Program Generation. Springer-Verlag, 2001.
[21] M. Torgersen, C. P. Hansen, E. Ernst, P. von der Ah,
G. Bracha, and N. Gafter. Adding Wildcards to the Java
Programming Language. In Proceedings of the 2004 ACM
symposium on Applied computing. ACM Press, 2004.
[22] D. Ungar and R. B. Smith. Self: The Power of Simplicity.
In Proceedings OOPSLA’87, 1987.
6
... Dynamically typed languages commonly support runtimeadaptable features such as reflection, metaprogramming, dynamic code generation, duck typing, and dynamic reconfiguration and distribution. The great runtime flexibility provided by dynamic languages has made them suitable for different scenarios such as runtime adaptable systems, rapid prototyping, dynamic aspect-oriented programming, and data processing and integration systems [1]. Taking the web development scenario as an example, Ruby [2] is used for the rapid development of databasebacked web applications with the Ruby on Rails framework [3]. ...
... Since both dynamic and static typing offer important benefits, there have been approaches aimed at obtaining the advantages of both approaches, following the philosophy of static typing where possible, dynamic typing when needed [1]. Out of all the theoretical research works, gradual typing has probably been the one with the highest impact [11]-soft typing [12], quasi-static typing [13] and hybrid typing [14] are other well-known works. ...
... Besides the robustness of detecting the error in line 7, the generated code provides significantly higher runtime performance by avoiding unnecessary casts and reflection (Section 4.1). 1 The first version of StaDyn only supported our extension of the var type, not dynamic. Afterwards, C# 4.0 was launched and included the new dynamic type. ...
Article
Full-text available
Hybrid static and dynamic typing languages are aimed at combining the benefits of both kinds of languages: the early type error detection and compile-time optimizations of static typing, together with the runtime adaptability of dynamically typed languages. The StaDyn programming language is a hybrid typing language, whose main contribution is the utilization of the type information gathered by the compiler to improve compile-time error detection and runtime performance. StaDyn has been evaluated as the hybrid typing language for the .Net platform with the highest runtime performance and the lowest memory consumption. Although most optimizations are performed statically by the compiler, compilation time is yet lower than the existing hybrid languages implemented on the .Net platform.
... Gradual Typing and Criteria. There is a growing number of research work focusing on combining static and dynamic typing (Thatte, 1989;Abadi et al., 1991;Meijer & Drayton, 2004;Hansen, 2007;Matthews & Findler, 2009;Wolff et al., 2011;Rastogi et al., 2012;Strickland et al., 2012;Boyland, 2014;Swamy et al., 2014). Many mainstream programming languages have some form of integration between static and dynamic typing. ...
... Many mainstream programming languages have some form of integration between static and dynamic typing. These include TypeScript , Dart (Bracha, 2015), Hack (Verlaguet, 2013), Cecil (Chambers, 1992), Bigloo (Serrano & Weis, 1995), Visual Basic.NET (Meijer & Drayton, 2004), ProfessorJ (Gray et al., 2005), Lisp (Moon, 1989), Dylan (Shalit, 1996), and Typed Racket (Tobin-Hochstadt & Felleisen, 2008). ...
Article
Full-text available
The semantics of gradually typed languages is typically given indirectly via an elaboration into a cast calculus. This contrasts with more conventional formulations of programming language semantics, where the semantics of a language is given directly using, for instance, an operational semantics. This paper presents a new approach to give the semantics of gradually typed languages directly. We use a recently proposed variant of small-step operational semantics called type-directed operational semantics (TDOS). In a TDOS, type annotations become operationally relevant and can affect the result of a program. In the context of a gradually typed language, type annotations are used to trigger type-based conversions on values. We illustrate how to employ a TDOS on gradually typed languages using two calculi. The first calculus, called λBg\lambda B^{g} , is inspired by the semantics of the blame calculus, but it has implicit type conversions, enabling it to be used as a gradually typed language. The second calculus, called λe\lambda e , explores an eager semantics for gradually typed languages using a TDOS. For both calculi, type safety is proved. For the λBg\lambda B^{g} calculus, we also present a variant with blame labels and illustrate how the TDOS can also deal with such an important feature of gradually typed languages. We also show that the semantics of λBg\lambda B^{g} with blame labels is sound and complete with respect to the semantics of the blame calculus, and that both calculi come with a gradual guarantee . All the results have been formalized in the Coq theorem prover.
... The debate whether statically typed languages are better for software quality than dynamically typed ones has been going on for quite some time. Proponents of static typing insist that "it allows early detection of some programming errors" [27] and that it leads to "better documentation in the form of type signatures" [25]. Advocates of dynamic typing, however, claim that "it was easier to get things right with short source code, in which code that was not too terse or verbose determined behavior, when all types could be coerced into strings for debugging" [24] and that "static typing is too rigid, and that the softness of dynamically [sic] languages makes them ideally suited for prototyping systems with changing or unknown requirements" [25]. ...
... Proponents of static typing insist that "it allows early detection of some programming errors" [27] and that it leads to "better documentation in the form of type signatures" [25]. Advocates of dynamic typing, however, claim that "it was easier to get things right with short source code, in which code that was not too terse or verbose determined behavior, when all types could be coerced into strings for debugging" [24] and that "static typing is too rigid, and that the softness of dynamically [sic] languages makes them ideally suited for prototyping systems with changing or unknown requirements" [25]. ...
Preprint
Full-text available
JavaScript (JS) is one of the most popular programming languages, and widely used for web apps and even backend development. Due to its dynamic nature, however, JS applications often have a reputation for poor software quality. As a type-safe superset of JavaScript, TypeScript (TS) offers features to address this. However, there is currently insufficient empirical evidence to broadly support the claim that TS apps exhibit better software quality than JS apps. We therefore conducted a repository mining study based on 604 GitHub projects (299 for JS, 305 for TS) with over 16M LoC and collected four facets of software quality: a) code quality (# of code smells per LoC), b) code understandability (cognitive complexity per LoC), c) bug proneness (bug fix commit ratio), and d) bug resolution time (mean time a bug issue is open). For TS, we also collected how frequently the type-safety ignoring `any` type was used. The analysis indicates that TS apps exhibit significantly better code quality and understandability than JS apps. Contrary to expectations, however, bug proneness and bug resolution time of our TS sample were not significantly lower than for JS: mean bug fix commit ratio was more than 60% larger (0.126 vs. 0.206), and TS projects needed on average more than an additional day to fix bugs (31.86 vs. 33.04 days). Furthermore, reducing the usage of the `any` type in TS apps was significantly correlated with all metrics except bug proneness (Spearman's rho between 0.17 and 0.26). Our results indicate that the perceived positive influence of TypeScript for avoiding bugs in comparison to JavaScript may be more complicated than assumed. While using TS seems to have benefits, it does not automatically lead to less and easier to fix bugs. However, more research is needed in this area, especially concerning the potential influence of project complexity and developer experience.
... As identified by Siek and Taha [26], there has been considerable interest in integrating static and dynamic typing, both in academia and in industry. There has also been a plethora of proposed approaches, from adding a dynamic keyword [2], to using objects in object-oriented languages [16], to Seik and Taha's gradual typing itself [23]. While there seems to be no one-sizefits-all approach to designing a system that mixes static and dynamic types, Siek and Taha standardize the guarantees [26] we can expect from such a system. ...
... Statically typed object oriented languages like C# and Java have worked to incorporate some form of dynamic typing [6,16]. C# 4.0 introduced the dynamic type to declare objects that can bypass static type checking [1]. ...
Preprint
Full-text available
In this paper, we describe our experience incorporating gradual types in a statically typed functional language with Hindley-Milner style type inference. Where most gradually typed systems aim to improve static checking in a dynamically typed language, we approach it from the opposite perspective and promote dynamic checking in a statically typed language. Our approach provides a glimpse into how languages like SML and OCaml might handle gradual typing. We discuss our implementation and challenges faced -- specifically how gradual typing rules apply to our representation of composite and recursive types. We review the various implementations that add dynamic typing to a statically typed language in order to highlight the different ways of mixing static and dynamic typing and examine possible inspirations while maintaining the gradual nature of our type system. This paper also discusses our motivation for adding gradual types to our language, and the practical benefits of doing so in our industrial setting.
... As identified by Siek and Taha [26], there has been considerable interest in integrating static and dynamic typing, both in academia and in industry. There has also been a plethora of proposed approaches, from adding a dynamic keyword [2], to using objects in object-oriented languages [16], to Seik and Taha's gradual typing itself [23]. While there seems to be no one-size-fits-all approach to designing a system that mixes static and dynamic types, Siek and Taha standardize the guarantees [26] we can expect from such a system. ...
... Statically typed object oriented languages like C# and Java have worked to incorporate some form of dynamic typing [6,16]. C# 4.0 introduced the dynamic type to declare objects that can bypass static type checking [1]. ...
Chapter
Full-text available
In this paper, we describe our experience incorporating gradual types in a statically typed functional language with Hindley-Milner style type inference. Where most gradually typed systems aim to improve static checking in a dynamically typed language, we approach it from the opposite perspective and promote dynamic checking in a statically typed language. Our approach provides a glimpse into how languages like SML and OCaml might handle gradual typing. We discuss our implementation and challenges faced—specifically how gradual typing rules apply to our representation of composite and recursive types. We review the various implementations that add dynamic typing to a statically typed language in order to highlight the different ways of mixing static and dynamic typing and examine possible inspirations while maintaining the gradual nature of our type system. This paper also discusses our motivation for adding gradual types to our language, and the practical benefits of doing so in our industrial setting.
... Especially when using arrays or other containers, it is often desirable that a type system supports unsafe covariance. I.e., a variable of type S can be assigned to a variable of type T, although the co-and contravariance rules for the results and arguments for some method declared by S and T are not met (Meijer & Drayton 2004 (1); will result in a run-time error. ...
... It may end with runtime errors that do not provide any clear explanation. By forcing static types, we can help developers to manipulate uncertain data, but we lose in terms of flexibility [MD04]. 15 As stated by Gordon et al., [GHN + 14], "the purpose of a probabilistic program is to implicitly specify a probability distribution". ...
Thesis
Self-Adaptive Systems (SAS) optimise their behaviours or configurations at runtime in response to a modification of their environments or their behaviours. These systems therefore need a deep understanding of the ongoing situation which enables reasoning tasks for adaptation operations. Using the model-driven engineering (MDE) methodology, one can abstract this situation. However, information concerning the system is not always known with absolute confidence. Moreover, in such systems, the monitoring frequency may differ from the delay for reconfiguration actions to have measurable effects. These characteristics come with a global challenge for software engineers: how to represent uncertain knowledge that can be efficiently queried and to represent ongoing actions in order to improve adaptation processes? To tackle this challenge, this thesis defends the need for a unified modelling framework which includes, besides all traditional elements, temporal and uncertainty as first-class concepts. Therefore, a developer will be able to abstract information related to the adaptation process, the environment as well as the system itself.Towards this vision, we present two evaluated contributions: a temporal context model and a language for uncertain data. The temporal context model allows abstracting past, ongoing and future actions with their impacts and context. The language, named Ain’tea, integrates data uncertainty as a first-class citizen.
Article
Full-text available
This article allows to investigate benchmark between programming languages, with the objective of identifying the performance between the execution time and the memory use between the Java and Python languages, as well as, in three implementations of dynamic languages that combine the two aforementioned languages: Jython, Jpype, Py4J. According to the results, it is concluded that the language that obtains the best performance is Py4J. Resumen El presente trabajo permite investigar sobre pruebas de rendimiento entre lenguajes de programación, con el objetivo de identificar el rendimiento entre al tiempo de ejecución y el uso de memoria entre los lenguajes Java y Python, así como, en tres implementaciones de lenguajes dinámicos que combinan los dos lenguajes antes mencionados: Jython, Jpype, Py4J. De acuerdo a los resultados, se concluye que el lenguaje que obtiene el mejor rendimiento es Py4J.
Article
Some blockchain programs (smart contracts) have included serious security vulnerabilities. Obsidian is a new typestate-oriented programming language that uses a strong type system to rule out some of these vulnerabilities. Although Obsidian was designed to promote usability to make it as easy as possible to write programs, strong type systems can cause a language to be difficult to use. In particular, ownership, typestate, and assets, which Obsidian uses to provide safety guarantees, have not seen broad adoption together in popular languages and result in significant usability challenges. We performed an empirical study with 20 participants comparing Obsidian to Solidity, which is the language most commonly used for writing smart contracts today. We observed that Obsidian participants were able to successfully complete more of the programming tasks than the Solidity participants. We also found that the Solidity participants commonly inserted asset-related bugs, which Obsidian detects at compile time.
Article
Full-text available
Generalised algebraic data types (GADTs), sometimes known as "guarded recursive data types" or "first-class phantom types", are a simple but powerful generalisation of the data types of Haskell and ML. Recent works have given compelling examples of the utility of GADTs, although type inference is known to be difficult. It is time to pluck the fruit. Can GADTs be added to Haskell, without losing type inference, or requiring unacceptably heavy type annotations? Can this be done without completely rewriting the already-complex Haskell type-inference engine, and without complex interactions with (say) type classes? We answer these questions in the affirmative, giving a type system that explains just what type annotations are required, and a prototype implementation that implements it. Our main technical innovation is wobbly types, which express in a declarative way the uncertainty caused by the incremental nature of typical type-inference algorithms.
Article
Full-text available
This paper proposes extending popular object-oriented programming languages such as C#, VB or Java with native support for XML. In our approach XML documents or document fragments become first class citizens. This means that XML values can be constructed, loaded, passed, transformed and updated in a type-safe manner. The type system extensions, however, are not based on XML Schemas. We show that XSDs and the XML data model do not fit well with the class-based nominal type system and object graph representation of our target languages. Instead we propose to extend the C# type system with new structural types that model XSD sequences, choices, and all-groups. We also propose a number of extensions to the language itself that in-corporate a simple but expressive query language that is influenced by XPath and SQL. We demonstrate our language and type system by translating a selection of the XQuery use cases.
Chapter
Full-text available
This paper defines a set of type inference rules for resolving overloading introduced by type classes. Programs including type classes are transformed into ones which may be typed by the Hindley-Milner inference rules. In contrast to other work on type classes, the rules presented here relate directly to user programs. An innovative aspect of this work is the use of second-order lambda calculus to record type information in the program. 1 Introduction A funny thing happened on the way to Haskell [HPW92]. The goal of the Haskell committee was to design a standard lazy functional language, applying existing, well-understood methods. To the committee's surprise, it emerged that there was no standard way to provide overloaded operations such as equality (==), arithmetic (+), and conversion to a string (show). Languages such as Miranda 1 [Tur85] and Standard ML [MTH90, MT91] offer differing solutions to these problems. The solutions differ not only between languages, but within a language...
Article
Full-text available
We present a method for providing semantic interpretations for languages with a type system featuring inheritance polymorphism. Our approach is illustrated on an extension of the language Fun of Cardelli and Wegner, which we interpret via a translation into an extended polymorphic lambda calculus. Our goal is to interpret inheritances in Fun via coercion functions which are definable in the target of the translation. Existing techniques in the theory of semantic domains can be then used to interpret the extended polymorphic lambda calculus, thus providing many models for the original language. This technique makes it possible to model a rich type discipline which includes parametric polymorphism and recursive types as well as inheritance. A central difficulty in providing interpretations for explicit type disciplines featuring inheritance in the sense discussed in this paper arises from the fact that programs can type-check in more than one way. Since interpretations follow the type-checking derivations, coherence theorems are required: that is, one must prove that the meaning of a program does not depend on the way it was type-checked. The proof of such theorems for our proposed interpretation are the basic technical results of this paper. Interestingly, proving coherence in the presence of recursive types, variants, and abstract types forced us to reexamine fundamental equational properties that arise in proof theory (in the form of commutative reductions) and domain theory (in the form of strict vs. non-strict functions).
Conference Paper
Full-text available
We develop the mechanism of variant parametric types, inspired by structural virtual types by Thorup and Torgersen, as a means to enhance synergy between parametric and inclusive polymorphism in object-oriented languages. Variant parametric types are used to control both subtyping between different instantiations of one generic class and the visibility of their fields and methods. On one hand, one parametric class can be used as either covariant, contravariant, or bivariant by attaching a variance annotation—which can be either +, -, or *, respectively—to a type argument. On the other hand, the type system prohibits certain method/field accesses through variant parametric types, when those accesses can otherwise make the program unsafe. By exploiting variant parametric types, a programmer can write generic code abstractions working on a wide range of parametric types in a safe way. For instance, a method that only reads the elements of a container of strings can be easily modified so that it can accept containers of any subtype of string. The theoretical issues are studied by extending Featherweight GJ—an existing core calculus for Java with generics—with variant parametric types. By exploiting the intuitive connection to bounded existential types, we develop a sound type system for the extended calculus.
Conference Paper
Most:programming languages use static scope rules for associating uses of identifiers with their declarations. Static scope helps catch errors at compile time, and it can be implemented efficiently. Some popular languages - Perl, Tcl, TeX, and Postscript - offer dynamic scope, because dynamic scope works well for variables that "customize" the execution environment, for example. Programmers must simulate dynamic scope to implement this kind of usage in statically scoped languages. This paper describes the design and implementation of imperative language constructs for introducing and referencing dynamically scoped variables-dynamic variables for short. The design is a minimalist one, because dynamic variables are best used sparingly, much like exceptions. The facility does, however, cater to the typical uses for dynamic scope, and it provides a cleaner mechanism for so-called thread-local variables. A particularly simple implementation suffices for languages without exception handling. For languages with exception handling, a more efficient implementation builds on existing compiler infrastructure. Exception handling can be viewed as a control construct with dynamic scope. Likewise, dynamic variables are a data construct with dynamic scope.
Article
As software becomes more and more complex, it is more and more important to structure it well. Well-structured software is easy to write, easy to debug, and provides a collection of modules that can be re-used to reduce future programming costs. Conventional languages place conceptual limits on the way problems can be modularised. Functional languages push those limits back. In this paper we show that two features of functional languages in particular, higher-order functions and lazy evaluation, can contribute greatly to modularity. As examples, we manipulate lists and trees, program several numerical algorithms, and implement the alpha-beta heuristics (an Artificial Intelligence algorithm used in game-playing programs). Since modularity is the key to successful programming, functional languages are vitally important to the real world.
Article
SIMULA 67 is a general purpose programming language with a built-in simulation capability similar to, but stronger than that of SIMULA I. SIMULA 67 has been developed by the authors at the Norwegian Computing Center. Compilers for this language are now being implemented on a number of different computers. Other compilers are in the planning stage. A main characteristic of SIMULA 67 is that it is easily structured towards specialized problem areas, and hence will be used as a basis for special application languages. SIMULA 67 contains the general algorithmic language ALGOL 60 as a subset, except for some very minor revisions. The reason for choosing ALGOL 60 as starting point was that its basic structure lent itself for extension. It was felt that it would be impractical for the users to base SIMULA 67 on still another new algorithmic language, and ALGOL 60 has already a user basis, mainly in Europe and the Soviet.