In the previous post, I discussed an idea I had for handling dynamic typing in a primarily statically-typed language. In this post, I intend to first, describe the idea a little better, and second, explain what are the problems with it.
The basic idea is:
For example, consider a function signature like:
let f[A, B](arg1: Int, arg2: A, arg3: B, arg4): Bool = ...
This declares a function
f with two explicit type parameters
B, and four regular value parameters
arg1 is declared with a concrete
arg3 are declared as having types passed in as type parameters.
arg4 does not have an explicit type, so in effect it behaves as if the function had an extra type parameter
arg4 has type
When the function is called, the type arguments don't have to be passed explicitly; rather, they will be automatically provided by the types of the expressions used as arguments. So, if I call
f(42, "hello", 1.0, True), the compiler will implicitly pass the types
B, as well as
Bool for the implicit type parameter
In the body of
f, whenever the parameters with generic types are used, the corresponding type parameters can be consulted at run-time to find the approprate methods to call. For example, if
arg2.foo() is called, a lookup for the method
A will happen at run-time. This lookup might fail, in which case we would get an exception.
This all looks quite beautiful.
The problem is when you introduce generic data structures into the picture. Let's consider a generic list type
T is a type parameter. Now suppose you have a list like
[42, "hello", 1.0, True] (which you might have obtained from deserializing a JSON file, for instance). What type can
T be? The problem is that, unlike the case for functions, there is one type variable for multiple elements. If all type information must be encoded in the value of the type parameter, there is no way to handle a heterogeneous list like this.
Having a union type here (say,
List[Int|Str|Float|Bool]) will not help us, because union types require some way to distinguish which element of the union a given value belongs to, but the premise was for all type information to be carried by the type parameter so you could avoid encoding the type information into the value.
For a different example, consider you want to have a list objects satisfying an interface, e.g.,
List[JSONSerializable]. Different elements of the list may have different types, and therefore different implementations of the interface, and you would need type information with each individual element to be able to know at run-time where to find the interface implementation for each element.
Could this be worked around? One way would be to have a
Dynamic type, whose implementation would be roughly:
record Dynamic( T: Type, value: T, )
Dynamic type contains a value and its type. Note that the type is not declared as a type parameter of
Dynamic: it is a member of
Dynamic. The implication is that a value like
Dynamic(Int, 5) is not of type
Dynamic[Int], but simply
Dynamic: there is a single
Dynamic type container which can hold values of any type and carries all information about the value's type within itself. (I believe this is an existential type, but I honestly don't know enough type theory to be sure.)
Now our heterogeneous list can simply be a
List[Dynamic]. The problem is that to use this list, you have to wrap your values into
Dynamic records, and unwrap them to use the values. Could it happen implicitly? I'm not really sure. Suppose you have a
List[Dynamic] and you want to pass it to a function expecting a
List[Int]. We would like this to work, if we want static and dynamic code to run along seamlessly. But this is not really possible, because the elements of a
List[Dynamic] and a
List[Int] have different representations. You would have to produce a new list of integers from the original one, unwrapping every element of the original list out of its
Dynamic container. The same would happen if you wanted to pass a
List[Int] to a function expecting a
All of this may be workable, but it is a different experience from regular gradual typing where you expect this sort of mixing and matching of static and dynamic code to just work.
[Addendum (2020-05-31): On the other hand, if I had an ahead-of-time statically-typed compiled programming language that allowed me to toss around types like this, including allowing user-defined records like
Dynamic, that would be really cool.]
That's all I have for today, folks. In a future post, I intend to explore how interfaces work in a variety of different languages.
Copyright © 2010-2021 Vítor De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.
Powered by Blognir.