Why did Julia choose nominal typing over structural typing/traits?

Here are some references regarding 1.5 Language Problem:

Yes, you’re right; this is nontrivial for concrete types but should be relatively straightforward for a hierarchy of traits. It’s easy to check if one set of methods is a subset of another. If the main goal is code reuse (addressing the expression problem and related issues), the general algorithm input should be expressed in terms of behaviours rather than concrete types anyway, otherwise, it isn’t reusable. This kind of ‘static type inference’ can help clarify how different parts of the code interact and make Julia projects more “growable” and “scalable”.

I still disagree that this is an issue. It’s simply a consequence of having an open structural type system. Instead of explicitly declaring that a concrete type implements a specific trait (as in Rust), this is done implicitly, which greatly increases code reusability. However, as a result, one may need to select the appropriate projection from the space of behaviors available to the concrete data when actually calling the function, for example: f(concrete_variable_x as A, concrete_variable_y as C).

Such a trait system is essentially a way to build behaviour hierarchies for a concrete data that are independent of one another, in contrast to a rigid nominal type system. When calling a function f that handles concrete data along different dimensions of these hierarchies, one may need to specify the projection of data behaviour to use in the example you provided, compiler just can’t predict how you want your data to be interpreted if it has several possible orthogonal behaviour hierarchies.

Can such ambiguity occur for ‘trait types’? Certainly. However, Julia could detect this ambiguity statically and suggest further specialization or indicate that you must manually select the structural type subspace you want to use. For instance, if variables a and b are considered to implement a trait LazySparseArray, but f(a, b) only has methods for (LazyArray, SparseArray), (SparseArray, LazyArray), (LazyArray, LazyArray), and (SparseArray, SparseArray), an ambiguity arises. In this case, the user must decide which perspective to adopt to call f(a, b), and due to the ambiguity, there could even be a compile-time hint suggesting that further specialization (potentially more efficient algorithm implementation) for f(LazySparseArray, LazySparseArray) is possible. Arguably, this is beneficial since you get notified of potential issues up front rather than encountering them at runtime.

Please let me know if I’m overlooking anything. I don’t have extensive experience with programming language design, so I may be missing something obvious.

1 Like