I cannot express how thrilled I am to see Microsoft address this problem in their language, and to see them do it in such a well thought-out way.
In particular, I was kind of underwhelmed by the Task stuff they added in 4.0, because what I was expecting was something like this new await/async syntax - so now, seeing that they used the (comparatively primitive) Task infrastructure in order to build await/async, I feel bad for doubting them. So far on a cursory examination, these new features solve almost all my concurrency problems (at least, from the perspective of a compiler), and should let me throw out a ton of existing code that I've been maintaining. I basically wrote my own cooperative scheduler and infrastructure to do exactly this on top of the iterator features from C#2/3, and it's been a godsend, so it's going to be nice to have it in the compiler :)
In particular I'm pleased that instead of dictating that everyone use only cooperative threading or preemptive threading for concurrency, they're exposing both scheduling techniques with easy-to-use APIs that integrate well with each other. To me, this seems like a much better approach than picking a One True Concurrency Mechanism (like, for example, node did).
You can compose and BeginOp EndOp functions into a function that returns an Async<T>
I've probably screwed up the exact function name but here's the psuedo code.
Example:
Imagine a C# class as follows
public class Foo {
public AsnycResult BeginGetBar(int bar_id) { // something }
public Bar EndGetBar(AsnycResult result) {
// something
return bar;
}
}
Now to consume in F# create an extension method
type Foo with
member x.AsyncGetBar = Async.FromAsyncResult(x.BeginGetBar, x.EndGetBar)
Now to use it
let somefunc = async {
let x = new Foo();
let! bar = x.AsyncGetBar(10);
// do something with bar
}
What F# does is create a call back that is the rest of the function after the let!/do! statement, basically your code is split into a call back everytime you use a let!/do! binding.
For a more interesting example lets say you want an async file copy.
let copy s1:Stream s2:Stream =
let buffer:byte[] = Array.init 8192
let rec copy = async {
let! read = s1.AsyncRead(0,buffer.Length,buffer)
match read with
| 0 -> ()
| x -> do! s2.AsyncWrite
return! copy()
}
The nice part about this is that copy is a fully reusable piece of code async code. So anytime you're in an async block and need to copy a file, so...
With this new extension to the C# language you can do the same in C#, it's the whole point of this extension. Actually the C# way of doing it seems to be a bit nicer than F# even.
Even though I never fully got the hang of F#, I must agree on this one. I like C# as a language, but too many magic keywords just starts smelling wrong.
Having a compiler do work for you and simplify your code is nice and you wont see me complain about this feature in particular.
On the other hand, I've made enough monadic extension classes (NullSafety, Object-transactions, etc) and extension methods to cover up for short-comings as I've seen them in my projects, and it always bugs me how this effectively forces me to clutter up my syntax.
Consider the following code
var x = GetSomeObject() // can be null
.SubProperty // can also be null
.Indexer[index]; // can also be null
This code can cause NullReferenceExceptions all over, so to do it safely, you need to add a bunch of if (object == null) checks.
With my NullSafe-extensions, the expression looks like this:
var x = GetSomeObject().AsNullSafe()
.Do((x) => x.SubProperty)
.Do((x) => x.Indexer[index])
.Value; // can be null or default
Within the C# type-system, there is no way to get around this clutter and massive use of lambdas. Granted, it looks better than the corresponding code with manual null-checks for every step, but with lambda expression comes the cost of not being able to debug & correct code at runtime.
Magic keywords, as you put them, on the other hand increase the ability of the language to be tooled. You get better debugging, code completion, diagnostics etc. when what could be a library feature is implemented as a first-class language feature.
The other side of the coin is that many larger businesses use coding standards to restrict the idioms in use across the enterprise. When the language is flexible enough to freely create new idioms (like Lisp and Smalltalk in extremis - the notion of DSLs came from these for good reasons), many folks fear that every codebase in the company will become its own silo of idioms, increasing the ramp-up time of new developers coming in. When an idiom comes packaged up from the tool vendor, "in the box" as it were, however, they can (hopefully) rely on new devs coming in to be up to speed already; as well as fairly confident it's been thought through properly.
Furthermore, third-party libraries and software component vendors in general benefit by being able to assume that everyone knows and can support the idioms.
But a: F# has had this for a while, and b: this isn't a language feature. It's a general workflow feature, and async is just one way of using it. Contrast that to C#, where it's specific functionality baked into the compiler and not something that can be extended via a library.
Because in F# you can write that code today rather than in a year or two when C# 5.0 is released.
Also, the pattern used by F# to do this sort of thing is exposed to the user so you can create any kind of monad instead of just one for async. In F# this behavior is integral to the language and not just syntactic sugar for a couple 'special' cases. So instead of learning one syntax for continuations and another for async, sql, etc, etc. You just have one system to learn that can be applied to many different problems, and you don't have to wait for Anders to bless your problem as important enough to have syntactic sugar added.
The other thing to note is that there will be many 'special cases' about using this code that don't work with typical C# code because of its mutability. It's similar to all the weirdness around using the yield statement. Like how you can't have an out parameter on a method that uses a yield statement and some weirdness around exceptions.
So far it seems like they're doing a good job of reducing the special cases when it comes to this feature. It handles exceptions correctly, and you can 'await' any object that implements a trivial interface, so it's not just limited to the APIs they've decided to bless.
I'm well aware of what the implementation is. If you read the reference, closing over variables is one of the most common source of customer complaints about a "bug" in the compiler as the implementation is completely counter-intuitive until you look at the MSIL. Ergo, they should fix closures.
You misread your own link: closing over loop variables considered harmful. Closing over values rather than variables would make closures useless for a huge variety of purposes.
What people that get bitten don't get: closures are lazy. They aren't executed right away unless you want to.
That's their purpose, with the most common use-case being to postpone a piece of computation for later (until you've got everything you need).
So it cannot close over values, and if it did it would mean that you couldn't modify the values from the outer-scope (which you just closed over) ... making closures totally useless.
Well, F# doesn't allow closures to access local mutable variables, and they don't seem "totally useless". In the case of a for loop, F# seems more intuitive: if you loop and generate closures using the loop variable, each closure will get the value at the time its created.
This isn't as straightforward in C#, as there are no immutable local bindings. It's annoying, but given the overall language, is probably the best fit.
The way it seems counter-intuitive to me is that I think of each loop iteration as containing a fresh variable, and not the same variable being re-assigned to over and over again.
I wouldn't go so far to say as there is anything wrong in closures itself, as much as a special case with regard to how it plays out in combination with the way the iterator pattern is integrated into the language itself.
I agree that in the example provided it does seem counter-intuitive at first, but v is clearly a variable tied to the the loop and there is nothing that explicitly tells us if it is scoped outside the loop or to the code-block within the loop.
If anything, I'd say it's the implementation of the iterator pattern which is broken, not the closures themselves (which I've had zero issues with).
Internally, the new mechanism (currently) uses a method called 'MoveNext' to advance the continuation state machine; it may be leveraging existing iterator conversion support.
If people doubt that Microsoft still has smart hackers working there, you just need to follow Eric Lippert's blog. Even if you have no interest in C#, he has a lot of genuinely interesting thoughts on programming and related topics.
This is probably the best language feature I've seen since generics in C#. Actually, I think it may just simply be the best language feature in C#, period. I wish I could use it in shipping code right now.
Seriously. I've wanted something like this in javascript for ages. Given the complete async nature of modern javascript, it would be a huge win. On the other hand, async code in javascript (as it is now) does look cleaner than the equivalent on C#.
Maybe you'll find stratified js -- http://stratifiedjs.org/ -- interesting. I haven't tried it myself, but it seems to fit the bill. As a side note: IMHO C#'s use of only 2 keywords and method combinators is cleaner than stratified js' approach.
I'm not that thrilled with yet more specific syntax for one feature. F#'s approach of having a simple core that can be extended in-language (versus only via the compiler with new syntax) seems like a much more elegant approach.
But it's nice that async code in C# won't be as painful as it is today.
In particular, I was kind of underwhelmed by the Task stuff they added in 4.0, because what I was expecting was something like this new await/async syntax - so now, seeing that they used the (comparatively primitive) Task infrastructure in order to build await/async, I feel bad for doubting them. So far on a cursory examination, these new features solve almost all my concurrency problems (at least, from the perspective of a compiler), and should let me throw out a ton of existing code that I've been maintaining. I basically wrote my own cooperative scheduler and infrastructure to do exactly this on top of the iterator features from C#2/3, and it's been a godsend, so it's going to be nice to have it in the compiler :)
In particular I'm pleased that instead of dictating that everyone use only cooperative threading or preemptive threading for concurrency, they're exposing both scheduling techniques with easy-to-use APIs that integrate well with each other. To me, this seems like a much better approach than picking a One True Concurrency Mechanism (like, for example, node did).