-
Notifications
You must be signed in to change notification settings - Fork 20
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
Support byref-like parameters (e.g., Span) in local functions that don't capture and are only invoked #887
Comments
I'm personally still not a huge fan of this feature, though I probably will end up approving it. I like the clarity that, when using byrefs, there is a need to make your code first-order and flat, and eliminate nearly all nesting and closure capture, except where using objects to do closure capture. That said, I can kind of see the logic that Span will intrude into F# programming more and more. How serious is the problem? |
@dsyme, several methods in the BCL take a function that take |
If #765 were allowed, then this would naturally fall out of it from a compiler analysis standpoint. It was my main motivation for filing that suggestion. |
I've just been playing with That one blocks (certain) use in generic methods or types, but if the function doesn't capture anything, I think that error can go, which makes this suggestion applicable to a wider range of issues. |
Just to comment on this with a real-world case - I took a little dive into what it would look like to "spannify" some of the internals of the compiler. We do a large amount of copying of various buffers, especially strings: the perfect candidate for span. Additionally, all the places where we iterate over things without copying would speed up since spans elide bounds checks at runtime. I'm sure more sophisticated mechanisms could be used, but some of these code paths are exactly what the abstraction was made for. Hooray! Unfortunately, "spannifying" has four major problems:
I don't have a good solution for (1) other than replacing many options internally with a Separately, if we were fine with introducing a dependency on System.Memory to FSharp.Core (it's already present in .NET Core ... 🙂) then we could also "spannify" some internals of FSharp.Collections and get significant perf boosts with very very little actual code change, independently of these problems. |
Another case:
System.Text.Json.Serialization.JsonConverter has members with |
@mciccottidmg That is actually by design and not related to this suggestion. Partial application implies boxing, which contradicts the rules for byrefs. |
@cartermp is that still true when the byref is the last parameter of the function? Also, is there any documentation on boxing in relation to partial application? |
Pretty serious, simple things like the function below can't be defined as local functions. // Could also take span, same problem as it returns a span regardless.
let trimSpaces (str: string) =
str.AsSpan().Trim(' ') They have to be pulled out of context into the module as a private top level function, in this way F# is worse off than C#. When it comes to the entire set of suggestions for improving byref use in F# (#688 being a prime example) enabling local functions involving byref looks to be - by far - the biggest value for the least amount of work and complications. |
If we look the code in this C# example, what would the corresponding be in F#? var x = int.TryParse(iniFileLine.AsSpan(2, 5), out int y); This won't compile (FS0193 error): let y, x = Int32.TryParse (iniFileLine.AsSpan(2, 5)) You'd think implicit cast is not causing new string-instances, but this won't compile either (FS0412 error): let y, x = Int32.TryParse (string(iniFileLine.AsSpan(2, 5))) This does compile, but does the let y, x = Int32.TryParse ((iniFileLine.AsSpan(2, 5)).ToString()) Edit: This question was pointless. There is actually |
Local function that don't capture and are only invoked should allow the use of byref-like types such as Span. It would enable code like this:
Today, this emits three errors:
Although the last error is very clear about what's going on, there's really no good reason (other than compiler analysis being hard) to disallow the above code.
The existing way of approaching this problem in F# is to break out the local function into a separate one, optionally marked as
private
so it doesn't participate in scope pollution:However, an error should still be emitted under the following circumstances:
Pros and Cons
The advantages of making this adjustment to F# are:
The disadvantages of making this adjustment to F# are:
Extra information
Estimated cost (XS, S, M, L, XL, XXL): M
Related suggestions: #872
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
The text was updated successfully, but these errors were encountered: