Draft spec for records and pattern-matching in C#
roslyn.codeplex.com
roslyn.codeplex.com
I've only been a year away from C# and I was writing 3.5 code at that time. It's kind of amazing seeing how quickly C# has moved from year to year compared to it's closest competitors. I had the same experience when I was looking at web development after a couple years of not really touching it. It's really nice to just take a step back and realize how quickly these things are progressing.
And honestly, I'm okay with C#'s release schedule. Fix bugs rapidly and frequently, by all means, but it takes time to learn how to take the best advantage of new semantics. I could really use not having those semantics change immediately after figuring that stuff out.
If you dig into how it works and really grok it, you can do some outright crazy stuff with it. For example: http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642....
As to C# 5.0, I am assuming that the managed languages team at MS was well into designing and implementing roslyn and probably didn't see great value is contributing massive features to the old compliler, but Async/Await couldn't, well uhh, wait for roslyn to be finished.
I rewrote the scraper to use BlockingCollections and Take(), with async being used solely with the HttpClient (which really needs to learn how to play nicer with async in the face of network errors!). The code was more decoupled, and while slightly more difficult to follow because of this, far easier to test and debug because the working set of my application was explicit rather than hidden in tasks. This had the added benefit of making it easy to persist and restore state. For me, the concurrent collections introduced in .NET 4 have been the big winner (no more custom thread pools!).
Use of async needs to be matched carefully with the problem domain, especially in backend development.
I remember having my brain sufficiently blown when I realized just what it was doing and how powerful of a feature this was.
I probably didn't get it, but what I thought I saw was a bunch of cool FP concepts bolted on top of a pretty long-in-the-tooth OO language. About the time the guy added the third custom DSL, I decided I would never ever code like that.
I like C#. I love OO. And I'm crazy about pattern-matching. But why do we think that combining good things from other places, we always get something that's better?
Perhaps we should start defining languages by what they don't do? Otherwise, everything will do everything -- and that increases cognitive load on the developers and leads to lots of noob errors by those just starting out.
Could somebody explain to me what we would like to achieve with this?
What do you mean by this? And which type of polymorphism?
In any case, having the compiler tell you that your pattern matching is not exhastive, and even demand that it is, prevents a whole class of problems and enable you to expand your program and have the compiler help you if you forget to add some new case in a pattern matching.
For example (taken from the Wikipedia article on SML) I could define an algebraic datatype "shape" as such:
datatype shape
= Circle of loc * real (* center and radius *)
| Square of loc * real (* upper-left corner and side length; axis-aligned *)
| Triangle of loc * loc * loc (* corners *)
Then I could define an "area" function that takes this as a parameter and pattern-matches on its type as such, with a different implementation depending on the type: fun area (Circle (_, r)) = 3.14 * r * r
| area (Square (_, s)) = s * s
| area (Triangle (a, b, c)) = heron (a, b, c) (* call to Heron's formula function *)
Which is also syntactic sugar for: fun area shape =
case shape
of Circle (_, r) => 3.14 * r * r
| Square (_, s) => s * s
| Triangle (a, b, c) => heron (a, b, c)
If I were to implement the function like: fun area shape =
case shape
of Circle (_, r) => 3.14 * r * r
| Square (_, s) => s * s
and forget to include the Triangle case, I would get a compiler error: "match nonexhaustive". Furthermore if I were to include a redundant case or an unreachable case, the compiler would also throw an error informing me of that. Very useful in preventing runtime errors, and languages like Java or C/C++ simply do not provide this.No plans to add extra sugar to immediately switch on the arguments of a function, but I'm pretty sure that switch(arg) is not terrible overhead.
I've addressed exhaustivity in a different comment.
Well, yeah, if providing a good transition path for Java developers and a solid Java two-way interop story (including low-cognitive-overhead mapping of Java APIs) wasn't important, Scala would just be Haskell.
> Perhaps we should start defining languages by what they don't do?
There's plenty of languages that are defined by relatively pure adherence to a particular model and not letting you do things outside of it. Neither Scala nor C# have ever been among them (Java -- for one model of OOP -- and Haskell for pure functional programming are examples, not being that way is one of the things that distinguished C# from Java.)
> Could somebody explain to me what we would like to achieve with this?
With, what, exactly -- pattern matching in C#? Or the kitchen-sink approach to language features more generally?
The simplest improvement that confused me at first is the ability to declare a new inline local to handle an 'out' variable:
int x; foo(out x)
becomes
foo(int x)
This by itself is a quality-of-life improvement but it enables the really nice extraction of values for pattern matching. I'm happy to see this as a general language feature instead of a hack specific for records. (Was this already introduced in a proposed future C# version, and I missed it?)
The record classes considerably simplify the creation of simple data structures, trees, etc. This alone would kill thousands and thousands of lines of pointless duplicate code in JSIL, so I'm really happy to see it. Are the properties of record class instances read-only? (It's hard to tell from the draft spec. I'd be okay with mutable-by-default with opt-in readonly, but always-readonly would also be okay with me as a simpler design.)
The introduction of an overloaded 'is' operator that accepts user-defined arguments on the right-hand-side. You define the operator on a given type and give it out-parameters (and presumably it can accept value parameters, as well, so you can do more sophisticated matching), and the compiler converts that into a call to the matching function that, if successful, yields values from the match. Really clever way to introduce this - the syntax is slightly confusing, but it avoids introducing new keywords and almost all of this feature follows nicely from existing C# features.
And switch statements are expanded to be able to pattern-match! Lovely! This is a natural improvement given that switch statements are already compile-time sugar for dictionary lookups when you're switch()ing on non-integer values.
It seems like all of this is compile-time sugar that doesn't require any runtime changes. Is that true? If so, what version of the runtime do you think will be the minimum for Roslyn output that uses these features?
Otherwise it just seems like some sugar on switch.
Also, guarded matches would be nice.
If we do that, you could provide pattern matching for existing classes, even ones from metadata, by simply manually specifying an extension is-operator for the class.
Similar? Looks exactly the same to me (language syntax aside), immutable properties, hashcode, equals, toString, etc. Kotlin grabbed it as well for their record types, good idea, eliminates great swaths of boilerplate and mutable state at the same time.
Not pretty, but it works well enough for a pragmatic programmer.
may.Match(
() => print("None"),
val => print("Some: " + val))
Not very flexible, or performant, but covers the basics of common cases.[1]: http://journal.stuffwithstuff.com/2009/05/13/ml-style-patter...
As well as a generic extension on Object I started adding bespoke Match extension methods to a monads library that I've been working on [1] (which again was all about trying to coerce C# into a more expression based style).
So the Option monad Match method would work like so:
Func<int> res = (from x in DoSomething()
from y in DoSomethingElse()
select x + y)
.Match(
Just: v => v * 10,
Nothing: 0
);
Or, the Either monad: var result =
(from lhs in Two()
from rhs in Two()
select lhs + rhs)
.Match(
Right: r => r * 2,
Left: l => 0
);
[1] https://github.com/louthy/csharp-monadScala allows you to seal a type hierarchy to a single file. That would work, but it seems hacky -- a file feels more like an implementation detail than a language construct to me. For one, the C# spec doesn't mention files at all -- from its perspective all your code may as well be in one giant file.