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

IsByRefLike structs don't work in tail recursive loops or pipe operator #7828

Closed
Horusiath opened this issue Nov 7, 2019 · 8 comments
Closed

Comments

@Horusiath
Copy link
Contributor

@Horusiath Horusiath commented Nov 7, 2019

Motivation

One of the common scenarios in programming languages is situation when we want to iterate over the elements of the collection up to a given point, when our expectation has been satisfied and we don't need to iterate any longer. In many languages (like C#) this can be easily done with foreach loop combined with break/early return.

However in F# this is not the case. To achieve that the common patter is to use recursive loop, eg.:

let tryFind item array =
    let rec loop item array i =
        if i >= Array.length array then -1
        elif array.[i] = item then i
        else loop item array (i+1)
    loop item array 0

Which will be unrolled by F# compiler into equivalent implementation with early return included.

Problem 1

While this code works for old reference type collections, it causes a compiler problems when we talk about ref structs (like i.e. ReadOnlySpan). If we use example from the above and replace Array with span, what we get in the result is:

error FS0412: A type instantiation involves a byref type. This is not permitted by the rules of Common IL.

The same happens when we want to pass it explicitly using ReadOnlySpan<'a> inref type parameter.

Problem 2

Exactly the same error happens when, we try to pipe functions working over ref structs. Here I'm using a helper function:

let inline slice offset len (s: ReadOnlySpan<_>) = s.Slice(offset, len)

let run (s) =
    s 
    |> slice 0 10
    |> slice 0 5
@dsyme

This comment has been minimized.

Copy link
Contributor

@dsyme dsyme commented Nov 7, 2019

This is by design, you just have to write core code involving Span in a first order, flat way. Can you convert this to a language suggestion please? Though technically it is hard to make progress on this without considerably increasing our promises if exactly how F# will compile code, which in turn will have many edge cases and other consequences.

@Horusiath

This comment has been minimized.

Copy link
Contributor Author

@Horusiath Horusiath commented Nov 7, 2019

@dsyme I'm sorry, but I don't know, what's needed to change that into feature suggestion.

As I guess, missing early loop escape mechanism working with ref-struct collections is not something, that happened by design. While it's possible to work around it with some flag variable gymnastics, it quite possibly comes with its own performance implications (which is the primary reason to use ref structs).

As span API and ref-struct enumerators will (hopefully) grow in popularity, people in some areas will be missing this more and more.

@abelbraaksma

This comment has been minimized.

Copy link
Contributor

@abelbraaksma abelbraaksma commented Nov 7, 2019

Language suggestions can go here: https://github.com/fsharp/fslang-suggestions, I think that's what @dsyme meant.

@cartermp

This comment has been minimized.

Copy link
Contributor

@cartermp cartermp commented Nov 7, 2019

@Horusiath please file a suggestion here for recursive loops: https://github.com/fsharp/fslang-suggestions

There is a suggestion in place for handling use of functions like |> that are declared as inline: fsharp/fslang-suggestions#688

The issue with Problem 2 is that the general problem is not possible to solve.

@cartermp cartermp closed this Nov 7, 2019
@dsyme

This comment has been minimized.

Copy link
Contributor

@dsyme dsyme commented Nov 7, 2019

As I guess, missing early loop escape mechanism working with ref-struct collections is not something, that happened by design. While it's possible to work around it with some flag variable gymnastics, it quite possibly comes with its own performance implications (which is the primary reason to use ref structs).

Yes. It's possible this could lead us to add break/continue support, even if it's just for writing concrete, first-order loop code like this and is not entirely satisfactory, e.g. __break syntax etc.

@TIHan

This comment has been minimized.

Copy link
Contributor

@TIHan TIHan commented Nov 7, 2019

If we can have a feature suggestion issue soon, I can comment on this. I experimented with local functions and byref parameters last week; I have thoughts for a possible design.

@abelbraaksma

This comment has been minimized.

Copy link
Contributor

@abelbraaksma abelbraaksma commented Nov 8, 2019

Isn't that this one basically, where you indeed commented? fsharp/fslang-suggestions#688

@TIHan

This comment has been minimized.

Copy link
Contributor

@TIHan TIHan commented Nov 8, 2019

@abelbraaksma that is referring to inline functions that can have their generic type parameter be a byref-like. What I was talking about here is local functions that can have byref-like parameter/argument(s):

let topLevel () =
    let local (p: byref<int>) = p
    let mutable x = 1
    local &x
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants
You can’t perform that action at this time.