-
Notifications
You must be signed in to change notification settings - Fork 784
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 to F# local functions #5270
Comments
Wow. Why the difference between top level and local functions?? |
This is intended design I believe. Locals functions and lambdas cannot have byref-like parameters. We should probably update the error message and its range because that doesn't look too good. |
Could you please share the design considerations / decisions / technical issues behind local functions / local lambdas ? I guess it will help a bit to improve the developers mental model about F#. |
Note that we document what you can use these for here: https://github.com/fsharp/fslang-design/blob/master/FSharp-4.5/FS-1053-span.md#byreflike-structs
And I think the key bit is here: They declare struct types that are never allocated on the heap. That would imply that any use case where it would be heap-allocated is disallowed. Is that correct, @TIHan / @dsyme? To @zpodlovics's point, this should probably be called out more clearly in the RFC and with some negative examples showing what you cannot do. |
@cartermp I am perfectly fine with the Span documentation, in case more deep understanding needed the C# and F# testsuite (positive/negative examples) make things more clear. The whole F# team really did an amazing work here! Could you share any roadmap when we could expect Span support in .NET Core SDK ? My question is the reasons (any developer could accept/learn that these things should coded in a specific way, but my question is why it works on that way? eg.: compiled to X for non local function and compiled to Y for local functions, .entrypoint startup documented in coreclr Z and ECMA W doc) behind the limitations of local functions / local lambdas. The workaround is trivial, it's not worth the time to dig deep in the source code - but sometimes the language creators could share lot more interesting design details. Eg.: why this example is allowed and why not allowed when spanSum moved to a local function into the main entrypoint? open System
let spanSum (data: Span<byte>) =
let mutable sum = 0
for i = 0 to data.Length - 1 do
sum <- sum + int data.[i]
sum
[<EntryPoint>]
let main argv =
let span = "abcdefg"B.AsSpan()
printfn "%d" (spanSum span)
0 // return an integer exit code |
@zpodlovics This is fair. Looking at the spec, it doesn't specify the reasoning. In testing C#, I'm able to use byref-like parameters for local functions. Perhaps we could make a feature improvement to relax these rules. |
After talking with @dsyme , There isn't a notion of a distinction between a "local function" or lambda at the language spec; though it could in theory. There are even some small bits in later stages of the compiler that look at this distinction. This means today, a "local function" is just like a lambda. Which, a lambda is first class and has a type of |
Before this feature improvement starts, I would like to suggest to add the indirect calls supports to this list. Probably also worth to create a proposal/design document to this. It would be good to have a somewhat relaxed but single FSharpFunc concept to support every kinds of calls (~method handles in .net): "We use ldftn + calli in lieu of delegates (which incur an object allocation) in performance-critical pieces of our code where there is a need to call a managed method indirectly. This change allowed method bodies with a calli instruction to be eligible for inlining. Our dependency injection framework generates such methods." [1] [2] [3] "This proposal provides language constructs that expose low level IL opcodes that cannot currently be accessed efficiently, or at all: ldftn, ldvirtftn, ldtoken and calli. These low level op codes can be important in high performance code and developers need an efficient way to access them." [2] [3] [1] https://blogs.msdn.microsoft.com/dotnet/2018/08/20/bing-com-runs-on-net-core-2-1/ |
@zpodlovics can you file an issue on fsharp/language-suggestions for that? Thanks! |
Linking to here, since I forgot about this suggestion: fsharp/fslang-suggestions#765 Basically, we should extend compiler analysis similar to what @dsyme mentioned:
|
Just pointing out a workaround: anonymous function closures can't be written this way, but object expressions can. [<AbstractClass>]
type Closure () =
abstract member Execute : byref<string> -> string
let dostuff (x:string) =
let x' = sprintf "x %s x" x
{ new Closure () with
override __.Execute (s:byref<string>) =
sprintf "%s %s" x' s
}
let mutable x = "foo" in (dostuff "bar").Execute(&x) This allows you use the result as if it were a first-class function and to close over additional variables in the surrounding context (as long as they are not themselves byref/byref-like), at the cost of writing extra boilerplate type definitions at the top level for each local function type you need to convert. You can't pass the result to client code that expects a first-class function, but it's useful for internal implementation details involving lots of precomputed nested closures. This is a case where fsharp/fslang-suggestions#277 could help. |
Will this be semi-resolved with #9017 ? Because I think now local functions that don't close over anything will be emitted as as static class and a static member, for example as in #9299 (comment) . Please ignore if that's not right, I'm not 100% sure about that. |
@NatElkins that's only for delegates, not lambdas. Local functions are lambdas in the context of F#. You are partially right though; if they don't capture anything, always emitting local functions as static members if they don't capture anything will be part of the solution. Right now, this isn't true, as lifting local functions to be top-level decls are disabled when optimizations are off. The gist of supporting The majority of the work might simply be turning on optimizations to lift local functions to be top-level decls even with the compiler option |
This should be in fslang-suggestions. |
Closing in favor of fsharp/fslang-suggestions#887 |
I'm trying to convert examples from https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T, namely
The code
try to add converting to
int
The text was updated successfully, but these errors were encountered: