Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: let-in construct in C# #3718

Closed
mkosieradzki opened this issue Jun 27, 2015 · 14 comments
Closed

Proposal: let-in construct in C# #3718

mkosieradzki opened this issue Jun 27, 2015 · 14 comments

Comments

@mkosieradzki
Copy link
Contributor

I digged the issues database trying to find this one, but I failed. It's quite obvious one and very common among functional languages. So it is probably a duplicate but...

Rationale

I am writing a lot lambda expressions in every day code. I try to keep code as functional as it is reasonable. So most of my lambdas are expression lambdas and it could have stayed this way if... for peformance reasons I want to prevent multiple evaluations of some parts of expressions. Let's take a following example:

x => new {
    A = CostlyFunction(x).Field1,
    B = CostlyFunction(x).Field2,
    C = CostlyFunction(x).Field3 == 5 ? CostlyFunction(x).Field1 : CostlyFunction(x).Field2
}

In this case reasonable workaround is:

x => {
    var tmp = CostlyFunction(x);
    return new {
        A = tmp.Field1,
        B = tmp.Field2,
        C = tmp.Field3 == 5 ? tmp.Field1 : tmp.Field2
    }
}

But proper solution:

x => let tmp = CostlyFunction(x) in new {
   A = tmp.Field1,
   B = tmp.Field2,
   C = tmp.Field3 == 5 ? tmp.Field1 : tmp.Field2
}

It's effient, clear. In case of LINQ it allows easily analyze this code by simple subsititution of referenced variable with proper definition expression.

We can use also multiple statements in such situation.

x => let a = CostlyFunction(x), b = SecondFunction(x) in new {
    A = a.Field1,
    B = a.Field2,
    C = b.Field1 + a.Field1
}

In case simplicity is desired in System.Linq.Expressions. Null-coalescing operators could be implemented using let a = expr1 in a != null ? a.expr2 : null

Great point it allows to express a lot of concepts clearly and efficiently without falling back to statement lambdas what is a great value itself.

@paulomorgado
Copy link

I find that syntax not intuitive at all.

If I understood your intentions correctly, declaration expressions (#254) is what you're looking for:

x => (var tmp = CostlyFunction(x), new {
    A = tmp.Field1,
    B = tmp.Field2,
    C = tmp.Field3 == 5 ? CostlyFunction(x).Field1 : CostlyFunction(x).Field2
})

@mkosieradzki
Copy link
Contributor Author

@paulomorgado
It was one of the closest I found. But as far as I know there is no planned Sequential-Evaluation Operator in C# (C++ style comma operator) and your code snippet depends on it. Or maybe I am misunderstanding declaration expression somehow.

Nevertheless I would prefer let-in as it does not imply imperatively evalutation order, rather works on dataflow natural evalution order: computing aliased expression and then the real expression which is defacto function of the aliased expression. Other benefit of using aliasing semantics is that it can be easily interpreted by LINQ providers (by simple substitution) contrary to implementing Sequential-Evaluation Operator support which is actually much more expressive (closer to the statement list which I am trying to avoid in the first place here).

@mkosieradzki
Copy link
Contributor Author

BTW. I am definitely not insisting on this specific syntax. There are many ways to make it more C#-pish. Just proposing the mechanism that C# lacks in my opinion. Maybe even the syntax suggested by @paulomorgado looks way better and more intuitively.

@HaloFour
Copy link

The proposals for declaration expressions on CodePlex did include semi-colon expressions:

int foo = (int bar = baz(); bar * bar); // assign foo to the square of the result of baz()

This feature was never fully fleshed out and was on the list to be punted before declaration expressions were cut entirely. With the clock reset and time to perhaps work through the other details (like scope) perhaps it would be reconsidered.

Much of this smells like a weird expression version of with, though, and personally I find it on the wrong side of the line between succinct/terse.

@mkosieradzki
Copy link
Contributor Author

@HaloFour
Thanks for pointing out original CodePlex issue, because #254 seems to be rather incomplete.

In this issue: #115 there is a discussion regarding let keyword used as "readonly var". In think it would be worth to consider adding let with two syntaxes:

let foo = bar();

as a readonly local declaration statement: readonly var

var a = 2 + (let foo = bar() in foo.baz())

as an expression with semantics mentioned before.

I didn't write it, but my proposal assumes that variable declared is readonly. Of course underlying object can be mutated by method calls.

@orthoxerox
Copy link
Contributor

Your let is just another lambda in disguise:

x => ((tmp => new {
        A = tmp.Field1,
        B = tmp.Field2,
        C = tmp.Field3 == 5 ? tmp.Field1 : tmp.Field2
    })(CostlyFunction(x)));

@paulomorgado
Copy link

But with declaration expressions there won't be a delegate nor a delegate instantiation nor a delegate invocation. Not even a method.

@mkosieradzki
Copy link
Contributor Author

@orthoxerox
Semantics of this construct can be expressed this way in lambda calculus and I use this approach in my implementation of Null-propagating operator in some tools I develop.

But:

  1. You cannot define lambda the way you suggest (due to the way type inference works in C# and I would never say it's bad!!! - because if it was different we could end up with language like "Swift" which has so many type inference resolution ways that its compilation works in exponential time :) ) and you end up overspecifying types for your code just to make compilation end up in a predictible future :).
  2. It's difficult to use in LINQ (Expression) because it will be difficult for LINQ providers to analyze such code.
  3. Compiler can implement this way more efficiently using exactly the same implementation as it would as for declaration expressions with one exception: "variable" cannot be reassigned and thus is not affected by most of issues that were reasons for declaration expressions not to be implemented in C# 6.0.

@metatron-the-chronicler

Is it possible to make the inner portion of the let be a kind of function body? That is feeling I get when I look at this proposal, but that may just be because I have passing knowledge of this kind of construct from Clojure and other Lisps.

let args*{
//let body
}

usage

let( temp=SomeFunction()){
A=temp.F1;
B=temp.F2;
}

@alrz
Copy link
Member

alrz commented Nov 11, 2015

This should be closed as it addressed by #6182.

x => (var tmp = CostlyFunction(x); new {
   A = tmp.Field1,
   B = tmp.Field2,
   C = tmp.Field3 == 5 ? tmp.Field1 : tmp.Field2
})

@gafter
Copy link
Member

gafter commented Nov 20, 2015

You would be able to do this easily with #6182, but more likely sooner #5154 will do it for you (though not necessarily as pretty).

CostlyFunction(x) switch (case var tmp: new {
   A = tmp.Field1,
   B = tmp.Field2,
   C = tmp.Field3 == 5 ? tmp.Field1 : tmp.Field2
})

alrz referenced this issue Nov 20, 2015
This will diagnose a common typo of omitting a comma in a switch expression.
@mkosieradzki
Copy link
Contributor Author

Especially #6182 makes "let-in" redundant while being much more C#-pish, elastic and compatible with approach used in top-level expressions/scripting and has better scoping, it is also shorter.

@gafter
Copy link
Member

gafter commented Dec 6, 2015

@mkosieradzki Do you want to close this, then, in favor of #6182?

@mkosieradzki
Copy link
Contributor Author

Definitely

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants