From 38eb2c0d2f72c452301db40c12daeccb75f2d14d Mon Sep 17 00:00:00 2001 From: josh11b Date: Tue, 3 Aug 2021 10:20:46 -0700 Subject: [PATCH] Low context-sensitivity principle (#646) Establish a principle that understanding the meaning and performance should not depend on expensive context, and explain what makes context expensive. Co-authored-by: Chandler Carruth Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com> Co-authored-by: Richard Smith --- .../principles/low_context_sensitivity.md | 247 ++++++++++++++++++ proposals/README.md | 1 + proposals/p0646.md | 51 ++++ 3 files changed, 299 insertions(+) create mode 100644 docs/project/principles/low_context_sensitivity.md create mode 100644 proposals/p0646.md diff --git a/docs/project/principles/low_context_sensitivity.md b/docs/project/principles/low_context_sensitivity.md new file mode 100644 index 000000000000..fd14e6f053f6 --- /dev/null +++ b/docs/project/principles/low_context_sensitivity.md @@ -0,0 +1,247 @@ +# Carbon: Low context-sensitivity principle + + + + + +## Table of contents + +- [Principle](#principle) + - [Mitigations of context-sensitive costs](#mitigations-of-context-sensitive-costs) + - [Visual aids](#visual-aids) + - [Contextual _validity_ rather than _meaning_](#contextual-_validity_-rather-than-_meaning_) + - [Reduced cost of mistakes](#reduced-cost-of-mistakes) + - [Compiler-checked context](#compiler-checked-context) +- [Applications of the principle](#applications-of-the-principle) + - [Imports and namespaces](#imports-and-namespaces) + - [Name shadowing](#name-shadowing) + - [Flow-sensitive typing](#flow-sensitive-typing) + - [Coherence of names and generics](#coherence-of-names-and-generics) + - [Performance](#performance) + + + +## Principle + +Carbon should favor designs and mechanisms that are not sensitive to context. +Instead, we should favor constructs that are not ambiguous so that they don't +need context for disambiguation. This is in service to the goal that +[Carbon code is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write). +In particular, this is about prioritizing reading and understanding over +writing. We should be willing to trade off conciseness, which still benefits +reading as well as writing, for a sufficiently impactful reduction in the amount +of context needed to read and understand code. + +Context can be expensive in different ways, for example: + +- It can be _large_: it might require looking through a lot of lines of code + to find all of the relevant contextual information. +- It can be _distant_: the further away from the current declaration or + definition, the more expensive it is to find contextual information. This + can scale from a separate definition in the same file, to a separate file, + or even to a separate package. +- It can be _unpredictable_: it might require careful searching of large + bodies of code to locate the contextual information if its location cannot + be predicted. +- It can be _subtle_: the contextual clues might be easily missed or mistaken. + +Code that isn't context sensitive is easier to copy or move between contexts, +like files or functions. It is code that needs fewer changes when it is +refactored, in support of +[software evolution](/docs/project/goals.md#software-and-language-evolution). + +In general, we should start with more restrictive constructs that limit +ambiguity and see if we can make them work. If we find those restrictions are +burdensome, we will then have more information to inform the next step. Ideally +we would address those use cases with simple tools that solve multiple problems. +The goal is to make a bunch of orthogonal mechanisms, each of which are easily +understood and act in unsurprising ways. + +If that next step is to loosen restrictions, that is generally easier to do +while maintaining compatibility with existing code than adding new restrictions. + +### Mitigations of context-sensitive costs + +There are several ways that the potential costs of context-sensitive code can be +mitigated. These techniques can and should be leveraged to help minimize and +mitigate the contextual costs of Carbon features, and in some cases may provide +a path to a feature that would otherwise be prohibitively costly. + +#### Visual aids + +A direct way to reduce contextual costs is through lexical and syntactic +structures that form visual aids. These can both reinforce what the context is +and aid the reader in the expensive aspect of navigating the context. For +example, representing contexts with indentation, or IDE highlighting of matching +parentheses and braces. These visual hints make it easier for developers to +notice contextual elements. + +#### Contextual _validity_ rather than _meaning_ + +When the context only affects the _validity_ of code, but not its meaning, the +costs are significantly reduced. In that case, understanding the meaning or +behavior of the code doesn't require context, and a developer can easily rely on +the compiler to check the validity. A simple example of this is contextually +valid syntax, which is relatively common and inexpensive. However, reusing the +same syntax with different contexts _with different meanings_ shifts the +contextual information from simple validity to impacting the meaning of code. + +#### Reduced cost of mistakes + +Another mitigation for the costs of context-sensitive code is when the cost of a +mistake due to the context is low. Some simple examples: + +- Context-sensitivity in comments is less expensive in general than in code. +- In places where the general meaning is clear, developers can safely and + reliably work with that general understanding, and the context only provides + a minor refinement. + +##### Compiler-checked context + +Another way the costs of mistakes can be reduced is when the compiler can +reliably detect them. This is the fundamental idea behind statically +type-checked languages: the compiler enforcement reduces the contextual cost of +knowing what the types are. How early and effectively the compiler can detect +the mistakes also plays a role in reducing this cost, which is part of the value +proposition for +[definition-checked](/docs/design/generics/terminology.md#definition-checking) +generics. + +An example of this situation in Rust is that the same syntax is used for a move +and a copy of the value in a variable. Those cases are distinguished by whether +the type implements a specific trait, which may not be readily ascertained. The +compiler verifies that the code never uses a variable that is no longer valid +due to having been moved from, which is expected to catch the problems that +could arise from this difference. Otherwise the semantic difference between a +move and a copy is considered in Rust to be low-enough stakes for there to be no +need to signal that difference in the code. + +However, the reasoning that makes this example a good design on balance for Rust +doesn't necessarily apply to Carbon. The compiler is checking to prevent +_errors_, but it can't reliably check for unpredictable _performance_. Given +Carbon's priorities, that might make this level of contextual information still +too expensive. + +More background on this area of Rust specifically is presented in +[their blog post on language ergonomics](https://blog.rust-lang.org/2017/03/02/lang-ergonomics.html). + +## Applications of the principle + +There are many parts of Carbon that could potentially be analyzed through this +lens, and we can't enumerate them all here. This section focuses on several +examples to help illustrate how the principle is likely to be relevant to +Carbon. They focus on either cases that showcase the principle in effect or +cases which make challenging tradeoffs of the costs in the principle. + +### Imports and namespaces + +There are several parts of the way +[imports](/docs/design/code_and_name_organization/#imports) and +[namespaces](/docs/design/code_and_name_organization/#namespaces) are designed +in Carbon that reflect applications of this principle: + +- Adding an import or reordering imports should never change behavior of + existing code. This means the reader doesn't have to look through all the + imports to understand how code behaves. This is also important for tooling, + which should not have to worry about unwanted side effects when adding or + sorting imports. + +- Carbon doesn't provide an analogy to C++'s + [`using namespace`](https://en.cppreference.com/w/cpp/language/namespace#Using-directives) + or a + ["wildcard imports" mechanisms](/docs/design/code_and_name_organization/#broader-imports-either-all-names-or-arbitrary-code) + that merge the names from one namespace into another. Either would introduce + ambiguity in where a name is coming from, making the code more + context-sensitive. + +- Carbon doesn't support large blocks of code + [inside a namespace declaration](/docs/design/code_and_name_organization/#scoped-namespaces), + where the reader would have to search for the beginning of the block to see + what namespace applies. + +### Name shadowing + +We should limit how names can be reused with shadowing rules, so the meaning of +a name doesn't change in surprising ways between scopes. Further, if you find a +matching declaration you don't have to keep searching to see if there is another +that hides the one you found. This both expands the context you have to +consider, and is an opportunity to make a mistake identifying the correct +context, potentially leading to misunderstanding of the code. + +### Flow-sensitive typing + +This principle is an argument against +[flow-sensitive typing](https://en.wikipedia.org/wiki/Flow-sensitive_typing), +where the type of a name can change depending on control flow. For example, +[Midori used this for optional types](http://joeduffyblog.com/2016/02/07/the-error-model/#the-syntax). +If we were to support this in Carbon, you could unwrap an optional value by +testing it against `None`. + +``` +var x: Optional(Int) = ...; +if (x != None) { + // x has type Int. + PrintInt(x); +} +// x is back to type Optional(Int). +``` + +This can be taken farther, this example has `x` taking on three different types: + +``` +var x: Optional(Optional(Int)) = ...; +if (x != None) { + // x has type Optional(Int). + if (x != None) { + // x has type Int. + PrintInt(x); + } + // x has type Optional(Int). +} +// x has type Optional(Optional(Int)). +``` + +The concern here is that the context is very subtle. The type of `x` is affected +by otherwise ordinary-looking `if` statements and closing braces (`}`). + +While we might not want to completely eliminate the possibility of +flow-sensitive typing in Carbon, it would have to overcome a large hurdle. We +would only want a flow-sensitive feature if it delivered sufficiently large +usability, consistency, or expressivity gains. + +### Coherence of names and generics + +Carbon [packages](/docs/design/code_and_name_organization/#packages) are +designed to ensure all declared names belong to exactly one package and the +compiler can enforcement Carbon's equivalent +[one-definition rule (ODR)](https://en.wikipedia.org/wiki/One_Definition_Rule). +This avoids an issue in C++ where the ODR is not reliably checked by the +compiler, which can leave the correctness of programs dependent on both distant +and subtle contextual information. + +Similarly, Carbon generics should have +[coherence, like Rust](https://github.com/Ixrec/rust-orphan-rules#what-is-coherence), +where types have a single implementation of an interface. And this should be +enforced by the compiler, using rules like +[Rust's orphan rules](https://github.com/Ixrec/rust-orphan-rules#what-are-the-orphan-rules). + +### Performance + +Since +[Carbon's number one goal is performance](/docs/project/goals.md#performance-critical-software), +it is important that the performance characteristics of code be predictable and +readily determined by readers. This argues that those characteristics should not +depend on expensive context. For example, Carbon should not provide a +`dynamic_cast` facility with the same capabilities of C++'s where distant +aspects of the inheritance structure can cause surprising performance +differences. Similarly, Carbon should try to ensure normal looking method calls +and data member access don't have the surprising performance costs caused by +virtual inheritance in C++. + +More generally, Carbon should avoid features with hidden costs, particularly +when they scale based on subtle aspects of the context where those features are +used. diff --git a/proposals/README.md b/proposals/README.md index 7b4e61b8e175..49993c36266a 100644 --- a/proposals/README.md +++ b/proposals/README.md @@ -62,5 +62,6 @@ request: - [0601 - Operator tokens](p0601.md) - [0618 - var ordering](p0618.md) - [0623 - Require braces](p0623.md) +- [0646 - Low context-sensitivity principle](p0646.md) diff --git a/proposals/p0646.md b/proposals/p0646.md new file mode 100644 index 000000000000..f2a100c1242c --- /dev/null +++ b/proposals/p0646.md @@ -0,0 +1,51 @@ +# Low context-sensitivity principle + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/646) + + + +## Table of contents + +- [Problem](#problem) +- [Background](#background) +- [Proposal](#proposal) +- [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals) + + + +## Problem + +Carbon needs a consistent policy on how willing we are to make syntax dependent +on context. + +## Background + +Rust has +[a statement of their ergonomic principles](https://blog.rust-lang.org/2017/03/02/lang-ergonomics.html) +that talks about their approach to context dependence. + +## Proposal + +We propose adding +(`docs/project/principles/low_context_sensitivity.md`)[/docs/project/principles/low_context_sensitivity.md], +which has the details. + +## Rationale based on Carbon's goals + +This proposal supports the goal of making Carbon code +[easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write) +particularly prioritizing "read and understand" over "write". + +It supports +[software evolution](/docs/project/goals.md#software-and-language-evolution) by +allowing code to be moved or refactored with fewer changes. + +Further it supports developing +[performance-critical software](/docs/project/goals.md#performance-critical-software) +in Carbon by making performance predictable.