-
Notifications
You must be signed in to change notification settings - Fork 66
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
Pipe-Forward Operator #165
Comments
dotnet/csharplang#74 |
There is a deeper conversation on pipe forwarding in dotnet/csharplang#74 @bandleader mentioned. Just because more non-MS people are there, that's probably a good place to shake down ideas. @AnthonyDGreen I'm not clear on some of your pipe targets in the proposal. I think this is going to need boatloads of examples. I think I like a pipe looking operator like |>, but I think that is minutiae. I understand the relation with currying, but I'm not sure dropping the argument is the right fit for VB programmers. A placeholder allows the method call to still look the same, but "hey, put the value right here". This was also suggested in the C# thread, but as I looked at some of the samples, I was a bit concerned that having a bunch of placeholders (like @) where our brains are trained to think the same visual thing (a symbol) means the same thing, where here it is definitely not (except in the abstract, what the last thing had. Await feels like a core scenario for this proposal, and the approach you took ( -> Await ->) is different than a suggestion in the C# thread ( |> await). I don't yet have an opinion on this, but think I like Await first, but not yet keen on -> Await -> because I'm not sure people think of Await as something you pipe into , and that's what it looks like. What do each of these do: 'Not' | ( '.' | '!' | '?' | '...' | '.!' ) I think I particularly like the Cast operator. Dim i As Long
Dim j As Integer
j = i -> Integer ' Yep, blow up if it's too big |
For the example where:
is translated to this:
could the new syntax instead be something like this?:
(Taking cues from https://github.com/tc39/proposal-pipeline-operator.) Also, can you elaborate on why |
I like that prefixing Await in my VB head (the second one). I like |> because it's a pipe. Is how deeply ingrained that is an indicator that I'm old? It also looks somehow like a take all this (the pipe) and stick it in (the greater than) the next thing. Dunno, maybe just in my head, and not as important as feature. |
@KathleenDollard I had posted some feedback about this on @AnthonyDGreen 's scenario issue #154, including 1) why arrows like |
In this example, I can say this seems downright confusing to me, readability-wise. It looks like IsLetter requires no arguments. It is a symbol I now have to learn to parse. I'd say almost all of your examples appear to need a lot more decoding. I'd have to stare at them a while to see how they might translate to their traditional equivalents, and that's with those equivalents being right in front of me. The example above that one is far more confusing. At least I kind've understand the "side text(i) into the first argument of IsLetter()" in this example. There's a strong likelihood I would look at this code, and then look somewhere else for easier code to read. Is there anything that could be done with a word-based operator instead which might be easier to parse? |
|
Just checked the C# issue. They are proposing to allow 'expression continuation without backtracking' by piping to a new expression, with a obj.MAsync() -> Await -> @.ToString().Trim()
'And in the example Integer case:
Await GetSomeIntAsync() -> @ + 5 Not sure if I like it. IMHO my syntax solves this much more cleanly, and in perfect left-to-right order: |
I like the piping idea but I think it would be more in keeping with VB if it were an extension to member selection say ".>" and also ".>?" with no surrounding white space. In VB "x op y" is interpreted as "op(x,y)". That’s not what's happening here. I would also require ".<" as a placeholder for the piped value. Requiring the placeholder will provide the following: |
I don't see any benefit of the "pipe" operator but decreasing intuiitve readability. Extension Methods FTWExtension methods syntax is IMHO a far more better approach. Applying to the examples above:
This example ist just about the obj.MAsync().Await().NAsync().Await().ToString().Trim().Length
This is just because If text(i).IsLetter() Then
If args(0).IsNullOrEmpty() Then
obj.CallByName("Foo")
? str.Trim()
For i = 0 To arr.UBound()
? filename.GetExtension()
This is about the Dim accessor = getMethod.DeclaringSyntaxReferences(0).
GetSyntax(cancellationToken).
TryCast(Of AccessorStatementSyntax)?.
Parent.
TryCast(Of AccessorBlockSyntax)
Again, extension methods are a perfect fit here: Return compilation.GetPdbXml(qualifiedMethodName:=methodName).Parse()
This one is interesting as extensions methods don't play well with all method invocations here. I would prefer it like this: ilImage = File.ReadAllBytes(reference.Path).ToImmutableArray()
This is again a perfect match for extension methods changing Imports ReflectionExtensions
For Each item In obj.GetPublicProperties()
...
Next
It already says in the description that extension methods are preferred. Why not allow lambdas to be extension Methods, i.e. scoped extension methods? This would be a separate discussion on how to do this. Just one quick example: [Extension]
Dim remove = Function(s As String, value As String) value.Replace(value, "")
? someString.remove(badWords) |
@lds0m01 @hartmair I believe you are basically proposing what I suggested here (and also referenced in my comment above.) I was proposing a I also proposed the ability to use @KathleenDollard Would be happy to hear about feedback from the LDT. |
@bandleader Allowing any method regardless of File.Decrypt(Path.Combine(value, otherValue)) value..Path.Combine(otherValue)..File.Decrypt() or even value..Combine(otherValue)..Decrypt() I don't see the real benefit here but than confusion and far less intuiitive readability. If the .NET Framework lacks extension methods, then just adding/allowing the |
No feature is intended to be used in 100% of cases. If the feature didn't work with dotted methods, that wouldn't be a reason to shoot it down for the other 95% of cases (instance methods, local static methods, lambdas, etc.) However, I think the syntax could be adapted for use with dotted methods, using parentheses: File.Decrypt(Path.Combine(value, otherValue)) // becomes:
value..(Path.Combine(otherValue))..(File.Decrypt) That said, I don't think this example is a good candidate for piping in any case; as you indicated, it's clearer without piping. Nevertheless, people who do lots of complex operations using function composition find this feature essential. Giving examples is beyond the scope of the issue here, but feel free to consult docs for languages where this already exists. |
This proposal addresses Scenario #154 and partially addresses Scenario #164.
Summary
I propose adding a new operator to VB that passes its left operand as either the first argument or first operand of its right operand. This is similar to F#'s pipe-forward operator but different in that it passes its operand as the first argument rather than as the "last" as in F#. This difference both eliminates the requirement for function currying and is generally more applicable to how most .NET APIs are designed. In this way the
->
operator behaves similarly to an aggregate function in a queryInto
clause.Motivation
There are three primary benefits to this feature:
Object
with a fluent style:Detailed Design
The design for the built-in operators is straight-forward. The design for method invocations requires more explanation.
Precedence and Associativity
The big challenge with this feature is parsing precedence.
It's pretty obvious to which method obj is being passed in this example:
It's also obvious to which method obj is being passed in this example:
However, it's less immediately obvious to which method obj is being passed in this example:
And the default meaning is the later then potentially significant backtracking is now required to expression the former.
There's also an issue here of tooling; what completion options should appear and how should the final expression be formatted to indicate precedence.
To solve these questions this proposal gives
->
a higher precedence for argument lists than.
and also requires an explicit->
to transition back to normal associativity. This means if the target of the pipe is itself the result of a complex expression it's necessary to wrap that expression in parentheses:This still requires parentheses but keeps them after the
->
operator rather than requiring backtracking to before the left operand of the->
and it's easier to go back into normal.
precedence without any parentheses:But consequently to pipe into a method invocation with an implicit receiver requires parentheses:
Type-Inference and Overload Resolution
Overload resolution rules shouldn't change. It's an open question whether type-inference should work like extension method reduction where certain type arguments are fixed. This would give an experience consistent with extension methods but would suffer the same problems when constraints block inference entirely (unless we do something smart here).
Drawbacks
It's a new symbolic operator. Its meaning may not be immediately intuitive to a first-time reader.
Alternatives
Unresolved Questions
|>
like ML languages instead of->
? NoWith
block? Um, maybe...->
return the left operand if the target method is aSub
to enable fluent use of non-fluent APIs? No, because it would only work withSub
targets and would require a different solution forFunction
targets; lameThe text was updated successfully, but these errors were encountered: