In the following F# code, f1 takes a Span as input, and f2 calls f1. The compiler gives the indicated error when the argument is piped instead of passed.
let f1 (span: Span<byte>) = span.Length
let f2() =
let buf = [|0uy|].AsSpan()
buf |> f1 // FS0412
f1 buf // Ok
FS0412 A type instantiation involves a byref type. This is not permitted by the rules of Common IL
Is there a way to pipe a Span into a F# function?
CodePudding user response:
Just to add a little more color as to the why:
Byrefs and byref-like types fly in the face of functional-first programming.
These types are required to have their entire lifetime on the stack, and come with some compiler analysis that allow the runtime to elide some checks. That's great for performance.
When a function is made first-class, it involves a heap allocation. It's an object with an Invoke
method on it, basically. In the F# compiler there are several optimizations that attempt to convert an F# function into just a static method (and most F# function declarations are like this), but there are many circumstances where they're emitted as objects on the heap (some you can predict, some you can't). That means several things:
- You can't pass a function around that takes a byref or byref-like type as a parameter
- You can't use a byref or byref-like type in a lambda
- You can't use a byref or byref-like type in an inner function
There are some cases where this would technically be possible, but there's nothing in source code that would indicate why it's possible in some cases but not others. The reason would simply be "because the compiler needs to emit this function as an object" and that is entirely unpredictable and non-uniform. One proposed suggestion would help with this, but it's closed in favor of tweaks in the compiler and like this suggestion, which is estimated to probably not be too bad from a predictability standpoint.
Now the |>
case is more interesting, as are several other functions that are declared inline
. The |>
operator is quite literally defined to take in a higher-order function as a parameter, so naturally it shouldn't be supported. But because it's defined as inline
, it could actually work since it's just an optimization. However, this may also require that you can only use it in the context of other inline
functions. You may not be able to pipe into any arbitrary function.
That's why this is not a bug, but a by-design behavior that will take some serious consideration into enhancing, should it be implemented: https://github.com/fsharp/fslang-suggestions/issues/688