Sam Ruby on Continuations

Don Box's Spoutlet

Syndication

Sam has a very nice piece on bringing the old-school C heads into the world of continuations.
 
I for one fell in love with this style of programming when the first Whidbey C# compilers started appearing a couple of years ago.
 
Here's Sam's Fibonacci program in C# 2.0:
 
    class Program
    {
        public static IEnumerable<long> fib()
        {
            yield return 0;
            long i = 0, j = 1;
            while (true)
            {
                yield return j;
                long temp = i;
                i = j; j = temp + j;
            }
           
        }
        static void Main(string[] args)
        {
            foreach (long n in fib())
            {
                if (n > 1000)
                    break;
                Console.WriteLine(n);
            }
        }
    }
Except for the obvious syntactic differences, it's pretty much the same as the Ruby program (insert snide comment about how much cooler Ruby's assignment statement is).
 
I remember when the feature first showed up, people got pretty wild. I recall implementing a coroutine-like scheduler that modeled sequential execution as a series of yield return statements. Unusable in practice (at least my implementation) but cute.
 
The underlying language feature (along with its cousin, anonymous methods) do in fact rock though.
 
Here's the same program written using anonymous methods:
 
    class Program
    {
        public delegate T Function<T>();
        public static Function<long> fib2()
        {
            long i = 0, j = 1;
            return delegate()
            {
                long result = i;
                i = j;
                j = result + j;
                return result;
            };
 
        }
 
       
        static void Main(string[] args)
        {
            Function<long> f = fib2();
            while (true)
            {
                long n = f();
                if (n > 1000)
                    break;
                Console.WriteLine(n);
            }
        }
    }
 
This program has the same characteristics as the iterator version.  Calling the fib or fib2 function causes a heap-allocated frame to be created that keeps track of the particular activation of that frame.
 
One interesting thing about anonymous methods is that one can do Javascript-like prototype/instance stuff like this:
 
    class Program
    {
        public delegate T Function<T1, T>(T1 arg);
 
        public enum Op
        {
            Add, Subtract, Multiply, Divide
        }
 
        public static Function<decimal, decimal>[] CreateCalculator()
        {
            decimal value = 0;
            return new Function<decimal, decimal>[] {
                delegate (decimal arg) { value += arg; return value; },
                delegate (decimal arg) { value -= arg; return value; },
                delegate (decimal arg) { value *= arg; return value; },
                delegate (decimal arg) { value /= arg; return value; },
            };
        }
 
        static void Main(string[] args)
        {
            Function<decimal, decimal>[] calc = CreateCalculator();
            calc[(int)Op.Add](3);
            calc[(int)Op.Multiply](7);
            calc[(int)Op.Subtract](1);
            decimal result = calc[(int)Op.Divide](4);
            Console.WriteLine(result);
        }
    }

It may be a bit early to write off named class definitions, but it's nice that these ideas are easily expressible.

Posted Apr 17 2005, 05:36 PM by don-box

Comments

Sam Ruby wrote re: Sam Ruby on Continuations
on 04-17-2005 1:06 PM
Minor correction: I think you are referring to my Python program, and Python assignment statement (though Ruby has a similarly cool assignment statement, and can do things with blocks that neither Python nor C# could ever dream of doing, but that is a subject for another day).
Martin Smith wrote re: Sam Ruby on Continuations
on 04-17-2005 8:25 PM
Wow. That really blew my mind. I've spent most of the rest of my day picking up the pieces.

C# programming (for me, at least) just got about 10x cooler than it already is. With IronPython hot on its heels, I can't wait for the 2.0 release!
Sriram wrote re: Sam Ruby on Continuations
on 04-17-2005 11:48 PM
Instead of saying a "heap allocated frame", a more accurate description is that a hidden class is created and calling that method is actually calling an instance of that class - all through compiler magic.

That's a nitpick though- seriously awesome post
Stefan Tilkov's Random Stuff wrote Advanced C# Language Features
on 04-18-2005 12:41 AM
I never knew C# 2.0 supports yield &#8230; Don Box shows how to do continuations in C# - amazing....
Changing the way people think wrote Very worthwhile read for advanced C# users. ( #2 )
on 04-18-2005 2:10 AM
Highlighting the 'yield' keyword, and what effect it has on a function.&nbsp; There's some very cool...
Christian Mogensen wrote re: Sam Ruby on Continuations
on 04-18-2005 6:51 AM
yield return?
why use two words instead of one?

yield 0;
seems simpler (and more readable) than
yield return 0;

Could you yield something other than return values?
Dilip wrote re: Sam Ruby on Continuations
on 04-18-2005 7:24 AM
".....I recall implementing a coroutine-like scheduler that modeled sequential execution as a series of yield return statements......"

That particular beast was also implemented using the Fiber APIs in this[1] MSDN article.

[1] http://msdn.microsoft.com/msdnmag/issues/03/09/CoroutinesinNET/default.aspx
Joe Duffy wrote re: Sam Ruby on Continuations
on 04-18-2005 11:14 AM
Christian: choosing to implement yield as a context sensitive keyword was done to avoid compatability issues with C# 1.x syntax. For example, "yield x;" is a valid field declaration for a type named 'yield', but also would be a valid yield statement. Normally languages get around this with reserved keywords... but alas, C# 1.x has come and gone already...

Also, BTW, two related posts of mine that might be of interest:

Memoized streams:
http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=1c2de644-a85b-4b70-b605-d3fbdecaf1d0

and

C# 2.0 Ruby-isms:
http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=e016964b-19b0-43ee-a804-9ff2070082c7
Otaku, Cedric's weblog wrote Continuation: still not quite convinced
on 04-18-2005 11:57 AM
Sam Ruby and Don Box have posted a couple of interesting articles on continuations.&nbsp; Sam's article is a good explanation of what continuations are for &quot;old-timers&quot; and gives a...
Tom wrote re: Sam Ruby on Continuations
on 04-18-2005 3:31 PM
Ruby blocks are more like anonymous delegates than C#'s "yield return" generator stuff. Even though Ruby uses the yield word. The cool thing about Ruby blocks is that the callbacks (excepting that annoying parameter syntax and such like) look just like normal blocks. They blend right into the language.

Also, control statements like break, return, and such like also work like other blocks. So it makes for very clean syntax. C#'s "delegate" and JavaScript's "function" don't quite match it. And it does make a difference, I'd say.

Not to say I always favor Ruby, but I'd love to see this feature get around to more languages.
Mike wrote re: Sam Ruby on Continuations
on 04-18-2005 4:21 PM
"Not to say I always favor Ruby, but I'd love to see this feature get around to more languages"

Like Smalltalk perhaps? Maybe you should check it out. Ruby still doesn't have a development environment anything like Smalltalk.
Howard Lovatt wrote re: Sam Ruby on Continuations
on 04-18-2005 6:18 PM
I can't see that there is any advantage with continuations in an OO language. You can easily transform the continuation code into simple OO. EG:

[code]
class Fib {
int i = 0;
int j = 1;

int eval() {
final int result = i;
i = j;
j += result;

return result;
}

public static void main( final String[] notUsed ) {
final Fib f = new Fib();

for ( int n = f.eval(); n <= 1000; n = f.eval() ) {
System.out.println( n );
}
}
}
[/code]
Joe Duffy wrote re: Sam Ruby on Continuations
on 04-19-2005 2:06 PM
Howard, the code you wrote is approximately what C#'s implementation of 'iterators' does. It's really just syntactic sugar for a simple state machine.

Iterators are more like simulated coroutines--not full blown continuations. Writing true continuations on the CLR is tricky business, especially if you expect to preserve call stack state in order to restore it later on. The Northeastern Scheme folks realized this and wrote a paper which brings a tear to my eye. (Partially because it's a beautiful hack, although that people have to resort to that is not so good... wish we had better support in the EE for them.)

C#'s iterators are self contained units which don't rely on anything but execution stack in order to function. Continuations are more pervasive, and enable you to capture any arbitrary point of execution for return later on.
Patrick Smacchia wrote re: Sam Ruby on Continuations
on 04-20-2005 6:51 AM
For those who are not aquainted with C#2 anonymous methods/iterators, I wrote two articles on these subjects:

http://www.theserverside.net/articles/showarticle.tss?id=IteratorsWithC2

http://www.theserverside.net/articles/showarticle.tss?id=AnonymousMethods
MatHertel wrote re: Sam Ruby on Continuations
on 04-21-2005 12:53 AM
Between the lines I read that you think about removing the barrier between compiling and interpreting. Right?

We have to work too often with the complicate and slow reflection APIs or create external AppDomains.
Code injection (the good one) and code adoption in productive environments do solve some real live problems. Stuff like this can be raised up to the language level.
There definitely is a huge business impact for this.
snip:

class Program {
static functoid CalculateFormular;
...
Calc.CalculateFormular = new Function("x", "y", script_from_trusted_source);
Don Box wrote re: Sam Ruby on Continuations
on 04-21-2005 8:20 AM
Ah, Mat, am I that transparent? :-)

I think LCG in Whidbey is a huge feature along these lines, with more to come (I hope).

Cheers,
DB
Marc Brooks wrote re: Sam Ruby on Continuations
on 04-21-2005 8:38 PM
Is it just me, or does yield feel like a formalized (and nice) version of MA Jackson's program inversion technique circa 1975.

Check it out: http://www.25hoursaday.com/weblog/SyndicationService.asmx/GetRss
Steven Shaw wrote re: Sam Ruby on Continuations
on 04-24-2005 6:21 AM
It's worth pointing out that while these examples are great examples of the new C# 2.0 features, they are not examples of continuations in C#. C# does not support continuations.
Steven Shaw wrote re: Sam Ruby on Continuations
on 04-25-2005 7:19 AM
I got excited and install Visual C# 2005 Express Beta. I played around with the prototype example and got:

<pre>
using System;

namespace PrototypeLike
{
class Program
{
public delegate T Function<T>(T arg);

class Calc<T>
{
public Calc(Function<T> add, Function<T> sub, Function<T> mul, Function<T> div)
{
this.Add = add;
this.Sub = sub;
this.Mul = mul;
this.Div = div;
}

public readonly Function<T> Add;
public readonly Function<T> Sub;
public readonly Function<T> Mul;
public readonly Function<T> Div;
}

static Calc<decimal> CreateCalculator()
{
decimal value = 0;
return new Calc<decimal>(
delegate(decimal arg) { value += arg; return value; },
delegate(decimal arg) { value -= arg; return value; },
delegate(decimal arg) { value *= arg; return value; },
delegate(decimal arg) { value /= arg; return value; });
}

static void Main(string[] args)
{
Calc<decimal> calc = CreateCalculator();
calc.Add(3);
calc.Mul(7);
calc.Sub(1);
decimal result = calc.Div(4);
Console.WriteLine(result);
}
}

}
</pre>

I took away the second type argument to Function (it was T1). Not sure about that but it works.

I also changed the array structure to a "record" structure. This makes calling the "prototype" methods nicer.

Steve.
public virtual MemoryStream wrote C# Continuations useful? Hell yeah!
on 04-25-2005 11:55 PM
Last week, Sam Ruby posted a very interesting article on continuations for "people older than dirt" (a category which I, according to his definition, fall into). The topic became even more interesting when Don Box posted how you can use a very similar syntax in the next iteration of C#. Shortly thereafter, Cedric Beust posted that he wasn't convinced on...
Jelle Druyts wrote Generics at the Indigo service boundary
on 05-23-2005 4:05 PM
Anatoly Lubarsky: Weblog wrote .NET 2.0 -- Anonymous methods
on 07-06-2005 6:34 PM
Anatoly Lubarsky: Weblog wrote .NET 2.0 -- Anonymous methods
on 07-06-2005 6:34 PM
Sriram Krishnan wrote Functional Programming in C# - Currying
on 08-07-2005 10:04 AM
Everytime I make a post about C# or any other statically typed language, I get hit by a ton of comments...
abhinaba's WebLog wrote Anonymous methods and closures in C#
on 08-08-2005 7:13 AM
Anonymous Method
One of the new features in C#2.0 being shipped in with Whidbey is anonymous methods....
B# .NET Blog wrote C# 2.0 Iterators
on 07-06-2006 5:20 PM
Introduction
In my post about LINQ a couple of days ago, I promised to do a dive deep post on iterators...
John Dole wrote re: Sam Ruby on Continuations
on 01-02-2007 3:50 PM
This is not an example of continuations. This is an example of a closure that acts like a generator (the first code snippet acts purely like a generator with no closures).

A continuation would on the other hand return the value generated to a function that would be passed in as a parameter, something like

factorial( 5, (delegate(int num, delegateType next) { Console.WriteLine(num.ToString()); next(num); }));

Then the function factorial would create a new anonymous method each time that it would pass as the 2nd argument to itself that would know what to do with each new fibonacci # in the sequence.

As one can imagine, that would kill the stack pretty fast.. so you'd need to have tailcall optimization, where the current frame gets destroyed when you call the "next" function (which I don't think C# does right now, although there is a tailcall instruction).

Howard Lowatt, to answer your question, closures *are* essentially syntactic sugar (especially in C#) for classes. Local variables end up getting stored on the heap either way, but with using closure the class is hidden away only accessible by that one function that you call and no other functions in the class.

Add a Comment

(required)  
(optional)
(required)  
Remember Me?