Update: Don sets me straight, and rightfully so with a good exposition. I did not do my homework.
End Update
Don Box writes about compiling code on the fly in C# 3.0, kind of, I think...
Expression<Func<int, int>> expr = a => a + 3;
Console.WriteLine(expr); // prints "a => Add(a, 3)"
Func<int, int> func = expr.Compile(); // LCG's an MSIL method from the expr
Console.WriteLine(func(4)); // prints "7"
He equates (incorrectly from what I can tell) the above C# 3.0 with the following Scheme from 25 years ago...
(define expr '(lambda (a) (+ a 3)))
(display expr)
(define func (eval expr (scheme-report-environment 5)))
(display (func 4))
I think the comparison has some flaws. Maybe I just don't know enough about C# 3.0 (until just now I knew nothing about it).
First of all the expression (quote (lambda (a) (+ a 3))) is *data*, in particular a list of atoms and nested lists of atoms. That list is bound to a variable called expr
, whose name is conveying that the data *may* be evaluated as a lambda expression. But it is not a lambda expression, it is a list. The C# code appears to require something to be declared as an Expression
. Is such a class something that can be built programmatically? Or does it have to be declared and constructed intact?
Plus if it is already an expression, why would it need to be compiled? In the Scheme example, the list has to be evaluated to get a lambda, i.e. the function that the original list of atoms resembles. If you start with an expression, you should not have to do an eval or compile to get an evaluatable thing. That's just wrong. You'd just do this...
(define func (lambda (a) (+ a 3))) ; No QUOTE form. It's already a function.
(display (func 4)) ; Just apply it.
Or even more simply...
(display ((lambda (a) (+ a 3)) 4)) ; No QUOTE form. Just apply it.
One more thing: where is the QUOTE form in the C# code? The quote mark in Scheme is short hand for wrapping a special form (QUOTE ...)
around something that you consider "data". i.e. QUOTE prevents evaluation. Does the Expression
class automatically get recognized by the C# evaluator to prevent the RHS from being evaluated? Come on, either the RHS is data or the RHS is an expression. Or can the compiler only work with a special kind of data that happens to be called an Expression
. Weird any way you slice it from what I can make of it.
I don't get it, and maybe there is a more complete and logical explanation. Maybe the answer is "Pat, you're confused because you already know a simpler language like Scheme." Yeah, so why not use C++?
As it stands it looks like there is a bit of confusion and complication that separates C# 3.0 from simpler, more agile languages like Scheme, Python, and Ruby.
Just use those languages, folks. Don't get strung out on these incremental "improvements" to a language that was too complex to begin with.
8 comments:
Makes you wonder why Microsoft didn't just do a new Scheme implementation instead of bothering with all that support for different programming languages stuff?
"In Lisp or Scheme we would of course use quote and quasi quote to turn code into data and escape back to code. The problem with explicit quoting in Lisp is really the same as the HaskellDB mechanism; the API writer has to decide to use data or code, and then the user has to decide to quote or not.
One of the most exciting features of both C# 3.0 and Visual Basic 9 is the ability to create code as data by converting an inline function or lambda expression based on the expected static type of the context in which the lambda expression appears.
Assume we are given the inline function Function(X)X>42. When the target type in which that inline function is used is an ordinary delegate type, such as Func(Of Integer, Boolean), the compiler generates IL for a normal delegate of the required type. On the other hand when the target type is of the special type Expression(Of Func(Of Integer, Boolean)) (or any other nested delegate type), the compiler generates IL that when executed will create an intentional representation of the lambda expression that can be treated as an AST by the receiving API.
The major advantage of this style of type-directed quoting via Expression(Of ...) is that it is now (nearly) transparent to the consumer of an API whether to quote or not; the user only has to remember to use lambda expressions c.q. inline function declarations as opposed to ordinary delegate syntax."
"Confessions Of A Used Programming Language Salesman" p7
Hi Patrick,
In C#, there's no explicit quote operator. It can get away with this because it's statically typed -- if you write an anonymous function in a context that wants an object of function type, then it's unquoted. If you write an anonymous function in a context that wants an object of syntax tree type, then it's implicitly quoted.
Objects of syntax tree type have a method .compile(), which is like the eval function in Scheme.
This implicit quoting business seems a little overengineered to me, personally.
In C#, there's no explicit quote operator. It can get away with this because it's statically typed -- if you write an anonymous function in a context that wants an object of function type, then it's unquoted.
That has nothing to do with compile-time type checking. In Scheme (see the example above) there is a form for lambdas and a form for lists. Quote is not needed for lambdas.
Eval is not needed to apply lambdas in Scheme. But I see from yur comment and Don's code that an Expression is really a C# syntax tree. I'm not sure the explicit compile step is needed.
Forgot a sentence in the above...
The reason QUOTE is needed for lists is that the syntax for list data and function application is the same in Lisp. A list will be evaluated as a function application unless the QUOTE special form is used to prevent evaluation.
Thanks for the information about Expression, Isaac.
Looks convoluted to this former Lisp Weenie, but I guess it floats the boat.
Hi Patrick,
I really do mean that C# depends on the types.
In Scheme, if you write
(define id (lambda (x) x))
then the compiler will bind id to a function.
If you write
(define id-sexp (quote (lambda (x) x)))
Then id-sexp will get bound to an s-expression. The language uses the quote special form to know when to treat a form as Scheme code, and when to treat it as an s-expression.
Imagine that we have a language Scheme#, which is statically typed and has type annotations. Then you might write:
(define id:func (lambda (x) x))
(define id-sexp:sexp (quote (lambda (x) x)))
as before. But note that the type annotation makes the quote redundant -- you already know that id-sexp is bound to an s-expression. So you can write
(define id-sexp:sexp (lambda (x) x))
and it's not ambiguous whether the form beginning with lambda is an s-exp or a piece of Scheme code.
Basically, you're using the type annotations to figure out where to insert the appropriate calls to quote.
Thanks for the explanation Neel. I see your point that the type information is determining how to interpret the syntax.
That seems like more complexity to me. Now I cannot take the (lambda ...) out of its current context and easily use it elsewhere. One syntax, different context-dependent meaninings.
I am still just a Lisp Weenie at heart. Too much line noise for me.
But a good explanation of the noise. 8^)
Post a Comment