Developer productivity is a top design goal for Cone.
This does not mean that Cone will ever top the charts for how quickly a programmer can learn the language. Cone is too rich with features that are needed to facilitate performance, safety and code readability. Becoming an expert in properly using all these features is expected to take time and diligence.
Instead, Cone focuses on improving life-cycle agility for the knowedgeable, professional programmer. Since most of a programmer's time is spent on maintenance (often of someone else's code), the language design is prioritized around ensuring that Cone code is concise and expressive when read, and easy to refactor when improvements are required.
Reusability is a huge lever for productivity. For example, generics allow you to create one piece of code logic, and then reuse it by varying the types. Similarly, the whole point of packaged libraries is to enable programmers to get work done more quickly by leveraging the reusable work of others. Naturally, Cone supports both.
Cone takes the productivity advantages of reuse and polymorphism even further:
- Structural traits. In most languages, interfaces/traits are nominal, which means you have to declare them as the base class for every class you want to apply them to. Defensively, this means most people create an interface class for every concrete class. Since Cone's traits are structural, you only need to define a trait when you actually need to use it for something. Indeed, traits can defined be after-the-fact, in a different module from every type you want to apply it to.
- Delegated inheritance. Inheritance reuse differs from generics: Inheritance allows one type to reuse methods implemented by another type. Cone's delegated inheritance is a different flavor than typical inheritance, as it adds explicit forwarding delegation to composition, and it can be restricted to specified methods.
- Type extension. This feature enables a module to import a type from another module and then enrich it with additional methods to make it more useful for the needs of that module.
Type Inference and Coercion
Cone supports bidirectional type inference, which reduces how often type annotations need to be specified. Generally speaking, type annotations are usually required on function signatures and data constructors. In other cases, especially variable declarations, type annotations are often optional, as they can either be inferred or have a known default.
As a part of type inference, the compiler is also able to detect when it is safe to automatically coerce a value of some type to its equivalent supertype value. This eliminates the need for the programmer to explicitly convert the values.
Significant effort has been invested into making Cone's grammar simple and clear. The lesser benefit is that code creation is faster and easier to remember. The greater benefit is that the resulting concise, readable code is easier to understand and improve, especially when one's eyes are not distracted by unnecessary, space-consuming boilerplate.
Here are some examples of helpful syntactic sugar:
- Option operators. Nullable values can be expressed using the Option generic type. However, wrapping and unwrapping Option values can get grammatically long-winded. To reduce this clutter, there are several useful operators that may be used to unwrap Option values.
- Exception handling. In Cone, the Result generic type is used to support functions returning success or exception values. As with Option, operators may be used to facilitate unwrapping.
- Implicit return. In many cases, the final expression statement of a function is understand to be the returned value, and therefore does not require the 'return' keyword. Implicit return is consistent with Cone treating blocks and if/match structures as expressions. Just like return inference, the last value in each block is treated as the computed value for that block. Such consistency simplifies code refactoring and makes macro expansions more versatile.
- Operator Overloading. Infix punctuation operators are both concise and readily digestible, far more so than named functions and methods. Cone allows any type to define appropriate methods for a large number of these operators, include equality comparisons, pattern matching, arithmetic and logic operators, and even various operator assignments (e.g., '+=').
Tuples (ad hoc structs) improve the convenience of working with multiple values simultaneously.
Implicit use of tuples avoids the code overhead involved with declaring the data structure
and then individually moving around (destructuring) each element of the tuple.
Important use cases for tuples are parallel assignment, multiple return values and
marshalling queued-messaging data for use by a thread or actor.
a,b = b,a // parallel assignment used to swap values success, result = action(val) // multiple return values
You may have noticed (from code examples) that Cone supports Python-like significant indentation, where a block begins with a colon, and subsequent indented lines are considered part of the block.
The primary reason for this choice is code readability. When we are not wasting whole lines to hold a single curly brace, more of the code logic is visible in the editor. It is easier to digest what code is doing when the bulk of the logic can be seen together without scrolling.
If you prefer curly braces (and statement-ending semicolons), go ahead and use them. Cone supports both styles.
As compilers go, the Cone compiler is pretty fast. Given how often programmers have to re-compile systems, this too contributes to developer productivity.