Replies: 11 comments
-
Very interesting. From what I gather the interest here is in filling in the remaining squares of that matrix, namely with syntax to decompose and dispatch list and dictionary types, or more specifically for |
Beta Was this translation helpful? Give feedback.
-
These are my notes from what I presented yesterday morning at the LDM meeting, so I typed it up for use in the notes. The original motivation for working this out ~6 years ago was to set possible agenda items for filling in this matrix. Because the LDM membership has almost completely turned over since the first records proposal was written, some people don't recall the motivation for its syntactic choices and are considering alternatives that (in my opinion) could undermine its motivations. I tried to make the motivations explicit so that we could explicitly discuss their merits. |
Beta Was this translation helpful? Give feedback.
-
@gafter -
|
Beta Was this translation helpful? Give feedback.
-
I disagree with the repeated implication in the writeup that "shorter is better/easier". Perl is the quintessential counterexample to that. |
Beta Was this translation helpful? Give feedback.
-
I think the argument is that shorter naturally creates a pit of effort for users of the feature. Given two ways to do something a developer might tend for the version that requires less typing. There's certainly a legit argument that shorter can mean harder to read, but I don't think that these notes are arguing about that. In this case, if you have Might be more accurate to say that developers tend to use the features that have the "nicer" syntax. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Take pattern matching. It's an alternative to I hate to keep bringing it up, but I'll also point to the |
Beta Was this translation helpful? Give feedback.
-
and
.... I could have sworn F# allowed something along these lines:
... but the interpreters I'm trying have the |
Beta Was this translation helpful? Give feedback.
-
And it would have been pretty terrible to have used that anecdote to prevent what has been such a staggeringly useful and beneficial feature for the language. |
Beta Was this translation helpful? Give feedback.
-
That's not really the argument here. The argument is: if you're going to have multiple syntaxes, with varying levels of size and complexity, the short and simple syntax should represent the most valuable and desirable cases. That's the idea of pit of success here. Users should not have to add more and more syntax to get hte desired behavior. The desired behavior shoudl be the default and should be given by the simplest syntax. And opting out of that should require extra effort on their part. |
Beta Was this translation helpful? Give feedback.
-
When generics were introduced in C# 2, members of my team were against their use because the syntax was too hard to understand. Concerns about nested generic types were widespread enough that FxCop introduced CA1006: Do not nest generic types in member signatures Today, however, no-one blinks twice at the syntax of generics. All syntax is learned. All new syntax is unfamiliar and looks hard. (To be fair, I did blink (but only once) when I recently saw |
Beta Was this translation helpful? Give feedback.
-
There's also the option of introducing opinions in the compiler/IDE. For example, Roslyn could suggest that anything that can be readonly, should be. Obviously the language spec is independent of the compiler, but they're closely linked. I would say that a compiler suggestion like that would take Is this considered an insufficient way to introduce opinions? Is putting things in Roslyn considered cheating, because other compilers exist? I obviously understand that it's better to embed the preference directly into the language spec, but I'm curious how you weight compiler features as an effective tool here. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Programming languages are opinionated
Programming languages are opinionated, whether we like it or not.
Any given programming language makes some things easy, and other things not as easy.
If someone is programming and has to choose between the two, the language effectively encourages them to do things the easy way.
Sometimes the opinions of a language are intentional, but sometimes the opinions are incidental and do not align with the language author's own opinions.
When that happens, that is a language design smell.
When we design and modify a language, we should aim to have its opinions align with our own.
C# grew up opinionated
C# came preloaded with a set of opinions inherited from its predecessors.
virtual
(internal dispatch). The exceptions to this, inherited from non-object-oriented C, are the integral and enum types, for whichswitch
permits external dispatch.lock
) to provide mutual exclusion during mutation.Opinions have changed over time
As we've learned more about good software engineering practices, we've reconsidered these opinions and modified the language and its libraries accordingly.
We added extension methods to permit a non-
virtual
operation to be declared outside the type on which it operates while being used by the client as if it were a member.We added a number of enhancements to make more declarative (and less imperative) code possible, such as expression-bodied methods, Linq, the "elvis"
?.
operator, and local functions.We added libraries that support fork-join concurrency and asynchronous computation pipelines.
We added auto-properties to make it easier to declare APIs with immutable members.
In all of these, we moved the language's opinion not by making the existing things harder, but by adding yet easier ways of doing things that align with our opinions of what is "right".
The opinionated purpose of Programming With Data features
This cluster of features aims to move C#'s opinion:
One shortcoming of C#'s opinion is that internal dispatch is frequently inappropriate for programming-in-the-large (i.e. for data types that span component boundaries).
This is particularly true when the data type cannot anticipate all of the operations that clients will want to perform on it, or when the data type is described by a set of related types.
Extension methods do not help much because they are not virtual; they give the appearance of internal dispatch without actually providing support for it.
Two programming patterns that can be used to address this are the use of visitors, and switching on kind fields.
These are widely used throughout Roslyn.
Both permit constant-time dispatch based on the logical "kind" of the data type.
But these programming patterns are clumsy and error-prone.
C# 6 adds typeswitch (pattern-matching in the switch statement) as a more direct way of expressing external dispatch without these issues.
Typeswitch moves the language's opinion toward external dispatch, but currently it has a performance penalty that is enough to discourage many uses (which we are trying to address with runtime support).
The purpose of records and pattern-matching are to reorient C#'s opinions toward what are now understood to be better programming practices:
Example of a language design pitfall
It might seem that adding support for a hypothetical
readonly
modifier to parameters and locals would move the language's opinion in alignment with these directions.That is not so, for two reasons.
First,
readonly int x
is not shorter thanint x
, so the opinion has hardly moved.val
instead ofvar
is slightly better, but it moves an opinion from encouraging mutability of locals toward being neutral about the mutability of locals.But it adds the opinion that types for locals should be inferred, which is not intended.
It also doesn't make immutable preferred because it isn't shorter.
Second, the benefits of immutability occur at API boundaries, not within a method body.
So the hypothetical change, even if it moved the language's opinion, would not do so in accordance with the reason for wanting to move the opinion.
(I'm not saying the
readonly
modifier would be a bad idea, just that it doesn't fit with this theme.)For similar reasons, expression blocks are not particularly useful to move the language's opinion in a direction consistent with these preferences.
(Which is not to say it is a bad idea.)
A design principle for Programming With Data
There is a useful design criterion for this cluster of features that can make them more intuitive for programmers:
there should be a syntactic alignment between the principal operations on an exchange type.
The principal operations are declaration, creation, decomposiion, and dispatch.
We can draw a matrix of categories of types to see how we're doing so far:
int
int x
3
int x
int x
or3
class Point { public int X, Y; }
new Point { X = 3, Y = 4 }
p.X
Point { X: int x, Y: int y }
orPoint { X: 3, Y: 4 }
new { X = 3, Y = 4 }
p.X
{ X: int x, Y: int y }
or{ X: 3, Y: 4 }
class Point(int X, int Y);
(proposed)Point(3, 4)
(proposed)(int x, int y) = p
Point(int x, int y)
orPoint(3, 4)
(int x, int y)
(3, 4)
(int x, int y) = p
(int x, int y)
or(3, 4)
List<int>
new List<int> { 3, 4 }
Dictionary<K,V>
new Dictionary<K,V> { { K, V } }
This illustrates our attempt to align the syntax between the different forms in which a type is used.
That alignment helps build the programmer's intuition and simplifies understanding of the features.
Hopefully we can keep the syntactic forms simple and aligned to continue that tradition.
Beta Was this translation helpful? Give feedback.
All reactions