dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
18.91k stars 4.01k forks source link

C# Design Notes for Jan 21, 2015 #98

Closed MadsTorgersen closed 9 years ago

MadsTorgersen commented 9 years ago

C# Design Meeting Notes for Jan 21, 2015

Quotes of the day:

Live broadcast of design meetings: we could call it C#-SPAN

We've made it three hours without slippery slopes coming up!

Agenda

This is the first design meeting for the version of C# coming after C# 6. We shall colloquially refer to it as C# 7. The meeting focused on setting the stage for the design process and homing in on major themes and features.

  1. Design process
  2. Themes
  3. Features

See also Language features currently under consideration by the language design group.

1. Design process

We have had great success sharing design notes publicly on CodePlex for the last year of C# 6 design. The ability of the community to see and respond to our thinking in real time has been much appreciated.

This time we want to increase the openness further:

The C# 7 design team currently consists of

Anders, as the chief language architect, has ultimate say, should that ever become necessary. Mads, as the language PM for C#, pulls together the agenda, runs the meetings and takes the notes. (Oooh, the power!)

To begin with, we meet 4 hours a week as we decide on the overall focus areas. There will not be a separate Visual Basic design meeting during this initial period, as many of the overall decisions are likely to apply to both and need to happen in concert.

Feature ideas

Anyone can put a feature idea up as an issue on GitHub. We'll keep an eye on those, and use them as input to language design.

A way to gauge interest in a feature is to put it up on UserVoice, where there's a voting system. This is important, because the set of people who hang out in our GitHub repo are not necessarily representative of our developer base at large.

Design notes

Design notes are point-in-time documents, so we will put them up as issues on GitHub. For a period of time, folks can comment on them and the reactions will feed into subsequent meetings.

Owners and proposals

If the design team decides to move on with a feature idea, we'll nominate an owner for it, typically among the design team members, who will drive the activities related to the design of that feature: gathering feedback, making progress between meetings, etc. Most importantly, the owner will be responsible for maintaining a proposal document that describes the current state of that feature, cross-linking with the design notes where it was discussed.

Since the proposals will evolve over time, they should be documents in the repo, with history tracked. When the proposal is first put up, and if there are major revisions, we will probably put up an issue too, as a place to gather comments. There can also be pull requests to the proposals.

We'll play with this process and find a balance.

Other ways of increasing openness

We are very interested in other ideas, such as publishing recordings (or even live streaming?) of the design meeting themselves, and inviting non-Microsoft luminaries, e.g., from major players in the industry, onto the design team itself. We are certainly open to have "guests" (physical or virtual) when someone has insights that we want to leverage.

However, these are things we can get to over time. We are not going to do them right out of the gate.

Decisions

It's important to note that the C# design team is still in charge of the language. This is not a democratic process. We derive immense value from comments and UserVoice votes, but in the end the governance model for C# is benevolent dictatorship. We think design in a small close-knit group where membership is long-term is the right model for ensuring that C# remains tasteful, consistent, not too big and generally not "designed by committee".

If we don't agree within the design team, that is typically a sign that there are offline activities that can lead to more insight. Usually, at the end of the day, we don't need to vote or have the Language Allfather make a final call.

Prototypes

Ideally we should prototype every feature we discuss, so as to get a good feel fro the feature and allow the best possible feedback from the community. That may note be realistic, but once we have a good candidate feature, we should try to fly it.

The cost of the prototyping is an issue. This may be feature dependent: Sometimes you want a quick throwaway prototype, sometimes it's more the first version of an actual implementation.

Could be done by a member of the design team, the product team or the community.

Agenda

It's usually up to Mads to decide what's ready to discuss. Generally, if a design team member wants something on the agenda, they get it. There's no guarantee that we end up following the plan in the meeting; the published notes will just show the agenda as a summary of what was actually discussed.

2. Themes

If a feature is great, we'll want to add it whether it fits in a theme or not. However, it's useful to have a number of categories that we can rally around, and that can help select features that work well together.

We discussed a number of likely themes to investigate for C# 7.

Working with data

Today’s programs are connected and trade in rich, structured data: it’s what’s on the wire, it’s what apps and services produce, manipulate and consume.

Traditional object-oriented modeling is good for many things, but in many ways it deals rather poorly with this setup: it bunches functionality strongly with the data (through encapsulation), and often relies heavily on mutation of that state. It is "behavior-centric" instead of "data-centric".

Functional programming languages are often better set up for this: data is immutable (representing information, not state), and is manipulated from the outside, using a freely growable and context-dependent set of functions, rather than a fixed set of built-in virtual methods. Let’s continue being inspired by functional languages, and in particular other languages – F#, Scala, Swift – that aim to mix functional and object-oriented concepts as smoothly as possible.

Here are some possible C# features that belong under this theme:

A number of these features focus on the interplay between "kinds of types" and the ways they are used. It is worth thinking of this as a matrix, that lets you think about language support for e.g. denoting the types (type expressions), creating values of them (literals) and consuming them with matching (patterns) :

Type Denote Create Match
General T new T(), new T { x = e } T x, var x, *
Primitive int, double, bool 5, .234, false 5, .234, false
String string "Hello" "Hello"
Tuple (T1, T2) (e1, e2) (P1, P2)
Record { T1 x1, T2 x2 } new { x1 = e1, x2 = e2 } { x1 is P1, x2 is P2 }
Array T[] new T[e], { e1, e2 } { P1, P2 }, P1 :: P2
List ? ? ?
Dictionary ? ? ?
...

A lot of the matrix above is filled in with speculative syntax, just to give an idea of how it could be used.

We expect to give many of the features on the list above a lot of attention over the coming months: they have a lot of potential for synergy if they are designed together.

Performance and reliability (and interop)

C# and .NET has a heritage where it sometimes plays a bit fast and loose with both performance and reliability.

While (unlike, say, Java) it has structs and reified generics, there are still places where it is hard to get good performance. A top issue, for instance is the frequent need to copy, rather than reference. When devices are small and cloud compute cycles come with a cost, performance certainly starts to matter more than it used to.

On the reliability side, while (unlike, say, C and C++) C# is generally memory safe, there are certainly places where it is hard to control or trust exactly what is going on (e.g., destruction/finalization).

Many of these issues tend to show up in particular on the boundary to unmanaged code - i.e. when doing interop. Having coarse-grained interop isn't always an option, so the less it costs and the less risky it is to cross the boundary, the better.

Internally at Microsoft there have been research projects to investigate options here. Some of the outcomes are now ripe to feed into the design of C# itself, while others can affect the .NET Framework, result in useful Roslyn analyzers, etc.

Over the coming months we will take several of these problems and ideas and see if we can find great ways of putting them in the hands of C# developers.

Componentization

The once set-in-stone issue of how .NET programs are factored and combined is now under rapid evolution.

With generalized extension members as an exception, most work here may not fall in the language scope, but is more tooling-oriented:

This is a theme that shouldn't be driven primarily from the languages, but we should be open to support at the language level.

Distribution

There may be interesting things we can do specifically to help with the distributed nature of modern computing.

Also, await in catch and finally probably didn't make it into VB 14. We should add those the next time around.

Metaprogramming

Metaprogramming has been around as a theme on the radar for a long time, and arguably Roslyn is a big metaprogramming project aimed at writing programs about programs. However, at the language level we continue not to have a particularly good handle on metaprogramming.

Extention methods and partial classes both feel like features that could grow into allowing generated parts of source code to merge smoothly with hand-written parts. But if generated parts are themselves the result of language syntax - e.g. attributes in source code, then things quickly get messy from a tooling perspective. A keystroke in file A may cause different code to be generated into file B by some custom program, which in turn may change the meaning of A. Not a feedback loop we're eager to have to handle in real time at 20 ms keystroke speed!

Oftentimes the eagerness to generate source comes from it being too hard to express your concept beautifully as a library or an abstraction. Increasing the power of abstraction mechanisms in the language itself, or just the syntax for applying them, might remove a lot of the motivation for generated boilerplate code.

Features that may reduce the need for boilerplate and codegen:

With null-conditional operators such as x?.y C# 6 starts down a path of more null-tolerant operations. You could certainly imagine taking that further to allow e.g. awaiting or foreach'ing null, etc.

On top of that, there's a long-standing request for non-nullable reference types, where the type system helps you ensure that a value can't be null, and therefore is safe to access.

Importantly such a feature might go along well with proper safe nullable reference types, where you simply cannot access the members until you've checked for null. This would go great with pattern matching!

Of course that'd be a lot of new expressiveness, and we'd have to reconcile a lot of things to keep it compatible. In his blog, Eric Lippert mentions a number of reasons why non-nullable reference types would be next to impossible to fully guarantee. To be fully supported, they would also have to be known to the runtime; they couldn't just be handled by the compiler.

Of course we could try to settle for a less ambitious approach. Finding the right balance here is crucial.

Themeless in Seattle

Type providers: This is a whole different kind of language feature, currently known only from F#. We wouldn't be able to just grab F#'s model though; there'd be a whole lot of design work to get this one right!

Better better betterness: In C# we made some simplifications and generalizations to overload resolution, affectionately known as "better betterness". We could think of more ways to improve overload resolution; e.g. tie breaking on staticness or whether constraints match, instead of giving compiler errors when other candidates would work.

Scripting: The scripting dialect of C# includes features not currently allowed in C# "proper": statements and member declarations at the top level. We could consider adopting some of them.

params IEnumerable.

Binary literals and digit separators.

3. Features

The Matrix above represents a feature set that's strongly connected, and should probably be talked about together: we can add kinds of types (e.g. tuples, records), we can add syntax for representing those types or creating instances of them, and we can add ways to match them as part of a greater pattern matching scheme.

Pattern matching

Core then is to have a pattern matching framework in the language: A way of asking if a piece of data has a particular shape, and if so, extracting pieces of it.

if (o is Point(var x, 5)) ...

There are probably at least two ways you want to use "patterns":

  1. As part of an expression, where the result is a bool signaling whether the pattern matched a given value, and where variables in the pattern are in scope throughout the statement in which the pattern occurs.
  2. As a case in a switch statement, where the case is picked if the pattern matches, and the variables in the pattern are in scope throughout the statements of that case.

A strong candidate syntax for the expression syntax is a generalization of the is expression: we consider the type in an is expression just a special case, and start allowing any pattern on the right hand side. Thus, the following would be valid is expressions:

if (o is Point(*, 5) p) Console.WriteLine(o.x);
if (o is Point p) Console.WriteLine(p.x);
if (p is (var x, 5) ...

Variable declarations in an expression would have the same scope questions as declaration expressions did.

A strong candidate for the switch syntax is to simply generalize current switch statements so that

switch (o) {
case string s:
    Console.WriteLine(s);
    break;
case int i:
    Console.WriteLine($"Number {i}");
    break;
case Point(int x, int y):
    Console.WriteLine("({x},{y})");
    break;
case null:
    Console.WriteLine("<null>);
    break
}

Other syntaxes you can think of:

Expression-based switch: An expression form where you can have multiple cases, each producing a result value of the same type.

Unconditional deconstruction: It might be useful to separate the deconstruction functionality out from the checking, and be able to unconditionally extract parts from a value that you know the type of:

(var x, var y) = getPoint();

There is a potential issue here where the value could be null, and there's no check for it. It's probably ok to have a null reference exception in this case.

It would be a design goal to have symmetry between construction and deconstruction syntaxes.

Patterns at least have type testing, value comparison and deconstruction aspects to them.

There may be ways for a type to specify its deconstruction syntax.

In addition it is worth considering something along the lines of "active patterns", where a type can specify logic to determine whether a pattern applies to it or not.

Imagine positional deconstruction or active patterns could be expressed with certain methods:

class Point {
    public Point(int x, int y) {...}
    void Deconstruct(out int x, out int y) { ... }
    static bool Match(Point p, out int x, out int y) ...
    static bool Match(JObject json, out int x, out int y) ...
}

We could imagine separate syntax for specifying this.

One pattern that does not put new requirements on the type is matching against properties/fields:

if (o is Point { X is var x, Y is 0 }) ...

Open question: are the variables from patterns mutable?

This has a strong similarity to declaration expressions, and they could coexist, with shared scope rules.

Records

Let's not go deep on records now, but we are aware that we need to reconcile them with primary constructors, as well as with pattern matching.

Array Slices

One feature that could lead to a lot of efficiency would be the ability to have "windows" into arrays - or even onto unmanaged swaths of memory passed along through interop. The amount of copying that could be avoided in some scenarios is probably very significant.

Array slices represent an interesting design dilemma between performance and usability. There is nothing about an array slice that is functionally different from an array: You can get its length and access its elements. For all intents and purposes they are indistinguishable. So the best user experience would certainly be that slices just are arrays - that they share the same type. That way, all the existing code that operates on arrays can work on slices too, without modification.

Of course this would require quite a change to the runtime. The performance consequences of that could be negative even on the existing kind of arrays. As importantly, slices themselves would be more efficiently represented by a struct type, and for high-perf scenarios, having to allocate a heap object for them might be prohibitive.

One intermediate approach might be to have slices be a struct type Slice, but to let it implicitly convert to T[] in such a way that the underlying storage is still shared. That way you can use Slice for high performance slice manipulation (e.g. in recursive algorithms where you keep subdividing), but still make use of existing array-based APIs at the cost of a boxing-like conversion allocating a small object.

ref locals and ref returns

Just like the language today has ref parameters, we could allow locals and even return values to be by ref. This would be particularly useful for interop scenarios, but could in general help avoid copying. Essentially you could return a "safe pointer" e.g. to a slot in an array.

The runtime already fully allows this, so it would just be a matter of surfacing it in the language syntax. It may come with a significant conceptual burden, however. If a method call can return a variable as opposed to a value, does that mean you can now assign to it?:

m(x, y) = 5;

You can now imagine getter-only properties or indexers returning refs that can be assigned to. Would this be quite confusing?

There would probably need to be some pretty restrictive guidelines about how and why this is used.

readonly parameters and locals

Parameters and locals can be captured by lambdas and thereby accessed concurrently, but there's no way to protect them from shared-mutual-state issues: they can't be readonly.

In general, most parameters and many locals are never intended to be assigned to after they get their initial value. Allowing readonly on them would express that intent clearly.

One problem is that this feature might be an "attractive nuisance". Whereas the "right thing" to do would nearly always be to make parameters and locals readonly, it would clutter the code significantly to do so.

An idea to partly alleviate this is to allow the combination readonly var on a local variable to be contracted to val or something short like that. More generally we could try to simply think of a shorter keyword than the established readonly to express the readonly-ness.

Lambda capture lists

Lambda expressions can refer to enclosing variables:

var name = GetName();
var query = customers.Where(c => c.Name == name);

This has a number of consequences, all transparent to the developer:

For these reasons, the recently introduced lambdas in C++ offer the possibility for a lambda to explicitly specify what can be captured (and how). We could consider a similar feature, e.g.:

var name = GetName();
var query = customers.Where([name]c => c.Name == name);

This ensures that the lambda only captures name and no other variable. In a way the most useful annotation would be the empty [], making sure that the lambda is never accidentally modified to capture anything.

One problem is that it frankly looks horrible. There are probably other syntaxes we could consider. Indeed we need to think about the possibility that we would ever add nested functions or class declarations: whatever capture specification syntax we come up with would have to also work for them.

C# always captures "by reference": the lambda can observe and effect changes to the original variable. An option with capture lists would be to allow other modes of capture, notable "by value", where the variable is copied rather than lifted:

var name = GetName();
var query = customers.Where([val name]c => c.Name == name);

This might not be too useful, as it has the same effect as introducing another local initialized to the value of the original one, and then capture that instead.

If we don't want capture list as a full-blown feature, we could consider allowing attributes on lambdas and then having a Roslyn analyzer check that the capture is as specified.

Method contracts

.NET already has a contract system, that allows annotation of methods with pre- and post-conditions. It grew out of the Spec# research project, and requires post-compile IL rewriting to take full effect. Because it has no language syntax, specifying the contracts can get pretty ugly.

It has often been proposed that we should add specific contract syntax:

public void Remove(string item)
    requires item != null
    ensures Count >= 0
{
   ...
}

One radical idea is for these contracts to be purely runtime enforced: they would simply turn into checks throwing exceptions (or FailFast'ing - an approach that would need further discussion, but seems very attractive).

When you think about how much code is currently occupied with arguments and result checking, this certainly seems like an attractive way to reduce code bloat and improve readability.

Furthermore, the contracts can produce metadata that can be picked up and displayed by tools.

You could imagine dedicated syntax for common cases - notably null checks. Maybe that is the way we get some non-nullability into the system?

jamesbascle commented 9 years ago

@tophallen I think we can both agree on basically all of that. :smile:

colombod commented 9 years ago

You talking about things like const functions?

Dr Diego Colombo PhD

On 28 Jan 2015, at 07:17, Toni Petrina notifications@github.com wrote:

I would welcome any sort of "const"ness on method parameters. More powerful switch statement that can handle expression non-compile time constants would also help a lot.

— Reply to this email directly or view it on GitHub.

jamesbascle commented 9 years ago

@colombod I think he's talking about making mutable objects passed to functions or methods unable to be mutated within method scope. I personally think immutable type support would largely scratch that itch, but there are definitely some other cases.

It would likely have to come with some kind of performance hit, unless the logic for statically analyzing whether any of the methods called or properties accessed within the scope mutate the object in any way can be worked out at compile time.

tanghel commented 9 years ago

@MadsTorgersen first of all, I'm the biggest fan of val/let/readonly being a marker for immutability!

We already had a discussion at the Xamarin Conference about non-nullable reference types. What do you say about the following syntax:

Declaration:

public void MyMethod(string! value1, object! value2)
{
   ...
}

I can think about two approaches:

Guaranteed success:

string! stringValue = "Hello world!";
object! objectValue = new object();

Instance.MyMethod(stringValue, objectValue);

Usage with null check:

if (stringValue != null && objectValue != null)
{
    Instance.MyMethod((string!)stringValue, (object!)objectValue);
}

Usage without null check:

// compiler check will make sure you don't assign null
object! nonNullableObject = objectValue ?? new object(); 
object! nonNullableString = stringValue ?? string.Empty;

Instance.MyMethod(nonNullableString, nonNullableObject);

By putting the argument type in the signature of the method, you enforce the programmer to transform his variables into something that cannot explicitly receive the null value. The runtime should check assignments to these data types and will disallow them.

Non-nullable method parameters will be checked by the runtime as well and could result in ArgumentNullException (or maybe NullAssignmentException) that will always occur if you assign a null value to a non-nullable reference type.

tophallen commented 9 years ago

@jamesbascle Maybe then rather than bother implementing explicit capture syntax, we just implement explicit no capture syntax, like a keyword on the expression, i.e

var query = customers.Where(const c => c.Name != null);
//or other keywords, 'val' could be one or something else
var query = customers.Where(nocapture c => c.Name != "SomeName");

more similar to the async syntax for expressions, and maybe then there could be more freedom in how to implement the explicitly captured variables in an expression later, or at least, the more I think about various scenarios for it, the less happy I am with both my suggestions and the [name](c) => c.Name == name syntax proposed by @MadsTorgersen as no option really feels like the C# option.

ErikSchierboom commented 9 years ago

I really like the idea of having readonly parameters and locals, the less mutability the better. Too bad that we can't make them readonly by default, as that would of course break lots of code.

Although using val or let for readonly parameters or locals would result in less "noise" than readonly, I would still go with the readonly keyword. The readonly approach has the advantage that it is both more explicit and ties into a known keyword that has the same meaning. Perhaps there could be a shortcut syntax for the readonly keyword, similar to how Nullable<T> has a T? shortcut.

Some syntax comparisons:

public void Test1(readonly string str1, readonly string str2, readonly string str3)
{
    readonly str local;
}
public void Test1(val string str1, val string str2, val string str3)
{
    val str local;
}
public void Test1(let string str1, let string str2, let string str3)
{
    let str local;
}
public void Test1(string! str1, string! str2, string! str3)
{
    str! local;
}

So personally I think the readonly keyword should be used, with some shortcut syntactic-sugar to make it easy to use.

george-polevoy commented 9 years ago

Enum Types

I like the idea of typed enums, like in Java Enum Types.

This would allow for natural visitor pattern implemented directly in enum declaration instead of conditional switch, true singletons, etc.

wesnerm commented 9 years ago

Proper Tail Calls I would appreciate proper tail calls. I know that the 64-bit jitter does some of that.

Loops is a workaround for some cases, but the code potentially looks uglier.

Loops is not a workaround the lack of tail calls for mutually recursive functions. Or, when programming a continuation-style manner, to achieve elegant backtracking and other techniques.

Tail calls is actually very helpful when calling many higher order functions.

At the least, you can support a [TailCallAttribute] attribute.

AdamSpeight2008 commented 9 years ago

Traits would be good to be have if it they could be defined like classes.

trait Zero        { public static T Zero() }

trait Unit        { public static T Unit() }

trait op_addition { public static T [+] (T,T) }

trait op_subtract { public static T [-] (T,T) }

trait SimpleCalc
  inherits { Zero, Unit, op_addition, op_subtract }

trait op_EqualTo    { public static T [==] (T,T) }

trait op_NotEqualTo { public static T [!=] (T,T) }  

Traits the would be an extended form of constraited generics Then for example a Summation extension method could be generic.

T Sum<T> ( this nums : IEnumerable<T> ) 
  where T has trait SimpleCalc
{
  T total = T.Zero;
  foreach( num in nums )
    total = total + num;
  return T;
}

Enforced at compiler and runtime (the run cost could be worth it). It would be also applicable to constructors

has trait { new (Int,String) } // a constructor that takes an int and a string,
AdamSpeight2008 commented 9 years ago

IDEA: IsAnyOf and IsNoneOf for TypeOf expression.

leppie commented 9 years ago

Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary.

First line in the Scheme language specification

AdamSpeight2008 commented 9 years ago

Keep switch construct as it and have a match construct for pattern matching.

JauernigIT commented 9 years ago

+1 for Code Contracts at language level. I've ever been a fan of Code Contracts since .NET 4 and the DbC principle in general. I wrote about it in many articles.

Unfortunately, Code Contracts in their current implementation didn't spread very much in real-life projects. Imho this has several main reasons:

  1. Insufficient language support.
  2. Insufficient built-in tooling support in Visual Studio.
  3. No StyleCop/FxCop support (afaik).
  4. Limited static checking.

Code Contracts built into the language would be awesome, because some constructs have been really cumbersome (contracts on interfaces as an example). But furthermore, integration with tooling (VS, StyleCop, Pex, ...) is key to give real value. And the definition of useful contracts on all .NET core methods, because that's the requirement to let static contract checks work. And in my opinion static checking is the essential part to let code contracts succeed, including features like pure checking etc..

andrewducker commented 9 years ago

Enum generic constraints would be marvellous.

"Duck typing" generic constraints would also be very useful.

dzmitry-lahoda commented 9 years ago

Contracts in some way of some sort (mostly boilerplate, not static analysis).

C# 5:

public void Remove(string item)
{
   if (item == null) throw new ArgumentNullException("item"));
}

C# 6:

public void Remove(string item)
{
   if (item == null) throw new ArgumentNullException(nameof(item));
}

What if possible to drop Exception after throw keyword(like Attribute is dropped while applied):

public void Remove(string item)
{
   if (item == null) throw new ArgumentNull(nameof(item));
}

What if possible to drop new like in some languages:

public void Remove(string item)
{
   if (item == null) throw ArgumentNull(nameof(item));
}

What if possible to use kind of ? or ??:

public void Remove(string item)
{
   item ?? throw ArgumentNull(nameof(item));
   // item  == null ? throw ArgumentNull(nameof(item));
}

What if possible to get throw ArgumentNull via import of static function into namespace:

namespace System
{
public static class Exceptions
  {
    public static ArgumentNullException ArgumentNull(string name)
    {
       return new ArgumentNullException (name);
    }
  }
}

What if like try\catch\finally:

public void Remove(string item)
{
requires  //current Contract.EndContractBlock
{
  item ?? throw ArgumentNull(nameof(item));
}
contracted
{
//body
   ...
}
ensure
{
// compiled into some form into body to prevent assignment less then zero
 Count < 0 ? throw Argument(nameof(count),"Should be greater then 0");
}
}
m0sa commented 9 years ago

RE: Metaprogramming. Have you considered something like the ASP.NET 5 XRE's ICompileModule interface?

yetanotherchris commented 9 years ago

What happened to duck typing of interfaces? I realise it's not C# specific, but I'm sure it was part of 4.6, and it would be incredibly useful for shared libraries (such as logging; an ILog interface)

m0sa commented 9 years ago

@yetanotherchris assembly neutral interfaces are also an XRE-only thing

JauernigIT commented 9 years ago

@andrewducker +1 for duck typing of generic constraints.

kestasjk commented 9 years ago

All I have to say is: Yes please.

djelovic commented 9 years ago

@MadsTorgersen Array slices are an awesome idea (the current ArraySegment is a good start), but string slices would be even more useful. If you profile allocations of a typical .NET program most of of the allocations seem to be tiny strings. So something like struct StringSlice would be awesome.

(I created one for my projects but it breaks as soon as you call an existing API that expects strings, such as double.TryParse(). So it's one of those all-or-nothing kind of things.)

Also, are you guys doing any pointer escape analysis? Implicit boxing of small objects might not be so painful if the allocation was done on the stack.

zharchimage commented 9 years ago

It would be nice if you could await in getter and setter acessors of the properties using the async-await pattern. In the next release you can await inside the catch blocks, making it happen in getters and setters would be awesome!

AdamSpeight2008 commented 9 years ago

@MadsTorgersen For slicing an new operator .. could used, to discriminate from 2D array access.

a[ 2 .. 5 ]  // a[2] to a[3]

It be also applicable as a defining an range. 1 .. 100

daveaglick commented 9 years ago

@tanghel +1 for using '!' to denote non-nullable reference types similar to '?' for nullable value types. It's clean, concise, and matches up nicely with existing syntax. Of course the semantics of non-nullable reference types is another matter, but I like the syntax.

davepermen commented 9 years ago

@ErikSchierboom val would be like 'readonly var', so not for explicit types. there, readonly could stay.

a.k.a. string example = ""; readonly string example = "";

but var example = some.Funky().Query().FromLinq(); val example = some.Funky().Query().FromLinq();

i personally never use explicit types for variables.. only var everywhere. i'd love to instead use val everywhere.

davepermen commented 9 years ago

I'd like to see some nice syntax for IEnumerable to make it the default choice for people in more cases. my preferred syntax would be T*, but given the unsafe {} c++ style pointers there, that won't work.

ErikSchierboom commented 9 years ago

@davepermen Ah, then it makes a lot more sense! val is thus an alternative for var. I would then prefer let over val, as the difference between val and var can be quite hard to spot.

davepermen commented 9 years ago

that's true. but var is short for variable, val short for value. let is .. well.. nothing.. :)

but yes, readability might be an issue.

mariusGundersen commented 9 years ago

Something that is missing is syntax for accessing the field or property of a class. This is often done in linq chains, and is usually done using an expression:

//here x => x.name is an expression and reflection is used to find out which property is accessed
myDatabaseTable.select(x => x.Name);
//it could look like this
myDatabaseTable.select(DatabaseTableClass::Name);

ClassName::FieldName would be syntaxt sugar for typeof(ClassName)).GetField("FieldName").

select() could be defined as

public static TResult select<TInput, TResult>(this TInput self, FieldInfo<TResult> field){
  return field.GetValue(self);
}

Note that I am assuming FieldInfo is generic, which it isn't today, but should be (just like Type should be generic, like Class is in Java)

drewnoakes commented 9 years ago

Regarding lambda capture lists:

In a way the most useful annotation would be the empty [], making sure that the lambda is never accidentally modified to capture anything.

A simple, targeted solution to this is to have an alternative lambda 'arrow' syntax for non-capturing functions.

users.Select(user -> user.Active); // "->" requires non capture

Developers and IDEs can migrate existing code where possible, after which the guarantee is enforced.

It's not clear to me how copying a reference type would be enforced if a capture list allowed specifying copying. Would copy constructors have to be added to the language as well?

Giftednewt commented 9 years ago

let reminds me of the days when I was writing code in VB6 (fond memories), but I think I prefer it to val as it's significantly different enough from var that you could easily see whilst scanning code that the variable in question is readonly. I also think that let doesn't convey well enough that the variable is readonly.

The only alternatives I've thought of are rvar (readonly variable) or rloc (readonly local) or rov (readonly variable). Though I wouldn't consider any of those to be ideal, I think they convey the point of a readonly variable better than let.

ErikSchierboom commented 9 years ago

Perhaps val would also be fine, it certainly links it to the existing var keyword so it has that going for it. And now that I think of it, I did not have any problem with val and val doing the same thing that is proposed in Scala.

joshuaellinger commented 9 years ago

We've done one project in GoLang now and, despite some crippling design flaws, there is one area that it shines -- concurrency, ie. channels/goroutines. They have the same liberating effect on parallel processing that the property/method/events model has on component development. In other words, the syntactic sugars buys you a lot.

C# Task parallel library works as a weak substitute. You have to resort to block processing to get 1/2 the performance that GoLang gives you on scalar channels naturally. And it only works on a specific subset of problems.

We're done with GoLang due to its other flaws but we'd love to see C# copy channels/goroutines.

ps - If you think existing lambas and async/await get you the same effect, you need to use GoLang for a project to see the difference. It's not even close.

ufcpp commented 9 years ago

I sometimes see Japanese programmers are confused on distinguishing val and var in Scala, even though they are professional IT developers for years. It is difficult for Japanese to make out deference between L and R.

svick commented 9 years ago

@joshuaellinger Could you explain what exactly makes channels so much better than async-await? Is is the support for asynchronous sequences?

tanghel commented 9 years ago

@MadsTorgersen what about try/catch/finally aggregation?

Instead of:

int result;
try 
{ 
    result = GetValue(); 
} 
catch (Exception ex) 
{ 
    HandleException(ex); 
} finally
{ 
    DisposeResources(); 
}

... you could write:

var result = try GetValue catch HandleException finally DisposeResources;

And the elongated version could look like:

var result = try () => GetValue()
catch (Win32Exception ex) if (ex.NativeErrorCode == 0x00042) => HandleNativeException(ex)
catch (Exception ex) => HandleException(ex)
finally () => DisposeResources();

The trick would be to enable return values on a try block, as well as support lambda expression on try/catch/finally. As long as it's a one-liner, the resulting code looks much cleaner.

soc commented 9 years ago

@tanghel Yes, Scala has done this for years without issues. It's kind of weird in C# though, because try (and as proposed above, switch) would turn into an expression, but if would still be a statement. They either need to go the whole way or drop this proposal, imho.

igor-tkachev commented 9 years ago

@Giftednewt: What about def instead of let?

tanghel commented 9 years ago

@soc yes you are right, but you can use the conditional operator (?:) as an expression and assign it to a result variable. Changing the if clause to expression would most probably mean to include a then keyword as well. We'll see

soc commented 9 years ago

@tanghel

but you can use the conditional operator (?:) as an expression

The point is achieving some kind of consistency, not the lack of work-arounds.

Changing the if clause to expression would most probably mean to include a then keyword as well.

I don't see how this would follow from making if an expression.

pauldipietro commented 9 years ago

@igor-tkachev I think that as defis what's used in languages like Ruby and Python to define a method, it could cause some confusion. I personally would stick to val, or maybe something like imm or imut if you wanted to be very explicit in its purpose?

tanghel commented 9 years ago

@soc you have to mark somehow that the if clause has ended and that you're expecting a result expression, that's what I meant. Or maybe I don't see a more elegant way of doing it

conditional operator

var result = value == 10 ? true : false;

hypothetical if expression

var result = if value == 10 then true else false;
fubar-coder commented 9 years ago

@MadsTorgersen

@Romoku: I'd love to get non-nullable reference types into the CLR. It seems a tall order even there: what's inside an array of non-nullable reference type when it's first created? etc.

Why not doing something like the D array initialization?

This would create an array with all items set to an empty string.

string![100]! a = string.Empty;
soc commented 9 years ago

@tanghel (???) C# already has a syntax for if/then/else, it looks like if (...) ... else ... and works perfectly fine.

pdelvo commented 9 years ago

What is default(string!) going to be?

igor-tkachev commented 9 years ago

@orteipid Groovy and Nemerle use def to define immutable variables. Also Nemerle uses it to define local functions and I did not notice that it's confused.

tanghel commented 9 years ago

@soc yes you are right, my bad. C# enforces the brackets for the condition part of the if clause so you don't need a then keyword

jameshart commented 9 years ago

I think there's a few interesting additional rows in your table of 'kinds of types' that you could consider homogenizing into the same approach (following your lead, some syntaxes are just strawman speculations):

Type Denote Create Match
Option (Nullable<T>) T? explicit cast? !x
Sequence (IEnumerable<T>) T* (list, array, obvs. existing yielding generators; yielding lambdas? () => { yield return x; }) a :: b
Promise (Task<T>) T~ (? - obvs. async methods; async lambdas? async () => x? () ~> x?) await x

Something of a monadic theme developing here...

scalablecory commented 9 years ago

I'm very happy to see async sequences on the table. This is a big pain point for me right now.

ryancerium commented 9 years ago

@RichiCoder1 I'd argue that keywords on parameters is the C# way. Extension methods, varargs, ref, and out parameters are all keywords on parameter lists. Did I miss any obscure ones? You could select better keywords to be certain. That does leave the question of an empty capture list however... Perhaps (c, noscope) => c.Name == "Frank"; :-)