Casts: Dynamic / Ref Types
There are basically two kinds of type casts. First, those that change the
static type of an expression or reference type but do not change the value itself
only the way it is handled by the compiler.
(In Java the are implemented by the
checkcast bytecode with
the possibility to throw a runtime exception.)
Second, type conversions such as
(float) Math.PI that may
change the value they operate on (as to a lower precision value in this case).
The need to cast references could be considered a fault in the applications design. Take this example (mix between Fuzion and Java):
Person ref is name String is abstract Student(redef name String, id i64) : Person is study is .. Professor(redef name String, employee_id i32) : Person is teach is .. print_with_ids(l Sequence Person) is for p in l do if p instanceof Student say p.name + " id " + ((Student) p).id) if p instanceof Professor say p.name + " employee id " + ((Professor) p).employee_id)
Changing this code is inherently dangerous. Adding a new person kind
Assistant(redef name String, temp_id i32) : Person is work ..
may cause this code to fail since
Assistant is a case not handled
print_with_ids and the compiler has no way to detect this problem
The problem here is that code like this mixes two paradigms. An object-oriented approach with a choice type (union type). There are two ways to fix this, either by using choice types properly or by using the object-oriented approach properly.
Avoid casts with Choice Types
Using choice types, we could do this:
Student(redef name String, id i64) : Person is study is .. Professor(redef name String, employee_id i32) : Person is teach is .. Person : choice Student Professor is print_with_ids(l Sequence Person) is for p in l do match p stud Student => say(stud.name + " id " + stud.id), prof Professor => say(prof.name + " employee id " + prof.employee_id),
Assistant as above will cause a compile time error
unless this new type is added to the alternatives used in
and all the
match statements on
Avoid casts using object-oriented techniques
An object-oriented solution is straightforward:
Person ref is name String is abstract id_string String is abstract Student(redef name String, id i64) : Person is redef id_string => "id " + id; study is .. Professor(redef name String, employee_id i32) : Person is redef id_string => "employee id " + employee_id; teach is .. print_with_ids(l Sequence Person) is for p in l do say (p.name + " " + p.id_string)
Again, adding an
Assistant would cause a compile time error
id_string feature is implement properly.
Avoid casts by adding features to library code
Sometimes, desired functionality might be missing in an object-oriented design that was fixed in some library module. Say we have the following library
external_lib is Person ref is name String is abstract Student(redef name String, id i64) : Person is study is .. Professor(redef name String, employee_id i32) : Person is teach is ..
And we want to implement
print_with_ids in a different module without modifying
my_module is external_lib.Person .id_string String is abstract external_lib.Student .id_string => "id " + id external_lib.Professor.id_string => "employee_id "+ employee_id print_with_ids(l Sequence external_lib.Person) is for p in l do say (p.name + " " + p.id_string)
This approach of adding features is somewhat similar to implementing traits in Rust. There will have to be similar restrictions, i.e., the added features cannot be visible to the original library module or to other modules using that library.
Open question: Does it make sense to export added features if the module adding them is a library itself?
Runtime type checks using type variables
The workarounds to avoid type casts presented above have one important limitation. They do not work if the target of a type cast itself is not a concrete type but a type parameter. To illustrate this, I take an example from the paper A Reflection on Types by Simon Peyton Jones, Stephanie Weirich, Richard A. Eisenberg and Dimitrios Vytiniotis:
Say we want to provide an effect
ST that provides a way to create,
mutate and retrieve values of arbitrary types. Internally, the implementation
ST would use some abstract map to hold the actual values. What
type should elements in this map have?
Dynamic in Haskell
The solution in Haskell is to store
Dynamic values that consist
of a pair
represents the type of
For a type-safe way to extract a value of a given type from an instance
Dynamic, Haskell defines an operation
compare instances of
typeRep and add magic to the type checker such
that it knows that a successful
eqT implies that the types involved
instanceof in Java
Java's equivalent to Haskell's
Dynamic is the reference
Object in conjunction with
instanceof type checks
Dynamic types in Fuzion
Similar to Java,
Any is Fuzions most generic type. So we could
implement a state effect that can create slots to store values of arbitrary
types as follows:
state is # internally, we use some map from key to Any add_to_map(k key, v Any) is ... get_from_map(k key) Any) is ... create(T type) is k := create_key set(v T) => add_to_map k v read option T => v := get_from_map k match v a Any => a.cast_to T nil => nil
What we need for this is an intrinsic
Any as follows
Any ref is ... cast_to(T type) option T is intrinsic
ref values carry type information anyways, such an
intrinsic is easy to provide.
Type Casts in Fuzion
Fuzion should encourage the user to avoid type casts. Choice types and adding features to library code provide ways to avoid casts in cases where concrete types are used.
For the case of a cast to an abstract type defined by a type parameter,
cast_to intrinsic defined for
Any can provide the
necessary runtime type checks and be used for casts to types specified by type
cast_to could be used to cast
Any to value or to
ref types. In the following example, casts to ref types would succeed if
the runtime type is an heir type, while casts to value types would fail unless
the type is exactly right:
point(x, y i32). point3d(redef x, redef y, z i32) : point x y. test(T type, a Any) => say (a.cast_to T) a Any := point 3 4 test point a # ok test point3d a # nil test (ref point ) a # ok test (ref point3d) a # nil b Any := point3d 3 4 5 test point b # nil -- we cannot make a specific value type more general test point3d b # ok test (ref point) b # ok test (ref point3d) b # ok c Any := "some string" test point c # nil test point3d c # nil test (ref point) c # nil test (ref point3d) c # nil