For optimal performance, Cone supports a full set of CPU-friendly concrete, static types: integers, floating point, boolean, record structures, arrays, safe references, slices and raw pointers. More complex data structures are easily defined by composing these built-in primitive types. As helpful as this is, it is not enough for a modern language.
Programmers are short on time. They need to write a piece of logic once, and have it work across many data types and diverse operating environments. This kind of code reuse is called polymorphism. Cone offers a comprehensive range mechanisms that make polymorphism easy to accomplish: implicit, lossless type coercions, interfaces and traits, generics, and metaprogramming.
Programmers also need the ability to work with data whose precise type may not be known until the program runs. Such dynamic typing makes it easy to support heterogeneous collections, optional values, success/failure results, and more. Cone's variant type mechanisms make dynamic type versatility both easy and safe.
The following sections elaborate on these code reuse and variant type mechanisms.
Implicit Type Coercions
Sometimes, a value of one type can be safely coerced to another type. Cone recognizes when this is safe to do, and automatically coerces a value to the type expected by the variable or function it is being sent to. This makes it possible for a single function to serve the needs of diversely-typed values.
This is particularly useful for references, since:
- Allocated references coerce to borrowed references
- Nearly all reference permissions coerce to const
- Concrete types can coerce to traits or interfaces
- Non-nullable references can coerce to nullable references
If separate logic were needed for every combination of permission and allocator types, code could get quite verbose! Better yet, use of implicit coercions is "free": it neither bloats the size of generated code nor carries a runtime performance penalty.
Interfaces and Traits
Often a program uses several types that define identically-typed methods or fields. It can be useful for some part of the program to act on values of these types in the same way by virtue of this similarity. By restricting itself to a common subset, a code's behavior can be written in a general-purpose way, yet automatically specialize its logic based on the actual types of values, using either static or virtual dispatch.
Cone offers two distinct mechanisms for this:
- User-defined interfaces define type similarities structurally, defining only the method and field signatures in common. The advantage of interfaces is that they can be implicitly applied to any value whose type complies to the signature.
- User-defined traits define type similarities nominally, requiring all compliant types to explicitly declare they implement the trait. The advantage of traits over interfaces is their support for code and field inheritance. A type may implement (mix in) multiple traits.
Cone also supports a related dispatch capability: method overloading, which permits the creation of multiple methods that have the same name but different method signatures. This extends the ability of differently-typed values to participate in a named method's intended functionality, by dispatching to the method implementation whose type signature best matches the types of the method call arguments.
Generics enable the creation of type or function logic that works across all types that conform to specified interface or trait guard(s). Collection types are well served by this approach, as a single generic can apply the same algorithmic logic across all types the collection stores or indexes with.
Depending on the nature of the generic's parametric types, the instantiation of a generic can vary. Sometimes, instantiation is a compile-time "monomorphization" mechanism, using a macro-like mechanism to generate multiple versions for each type. Other times, the generic is transformed into a runtime mechanism that uses virtual dispatch to specialize the logic across values from many different types.
Metaprogramming supports polymorphism by enabling program logic to be re-shaped while it is being compiled. In effect, metaprogramming layers a syntactically-similar, compile-time "scripting language" over the core language, which alters or elaborates on the program's code being compiled.
Metaprogramming enables several helpful capabilities:
- Conditional compilation, which customizes a program's logic for its target runtime environment.
- Macros for concise specification of repetitive program logic or data.
- Compile-time execution for pre-calculating complex data structures.
Cone's variant types promote type versatility when we don't know what the type of a value is going to be until we run the program. User-defined variant types allow a compliant value to be one of several, seemingly-unrelated types. Variant types are useful for optional values (and nullable references), returned result/error, and many other uses. The type flexibility of dynamic languages, including their support for heterogeneous collections, is based on the use of variant types. Downcasting an interface reference to its original tagged type requires the use of variant types.
Information about the value's current type is carried as a part of the value. Pattern matching is used to safely extract a properly typed value for appropriate manipulation.
Similar to interfaces, variant types can also help reduce redundant logic. Variant types are more flexible in some ways than interfaces, since the types contained within a variant type need not share common methods. However, variant types do bear some added memory and performance costs.