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

tailrec to enforce tail recursion #11

Open
WhiteBlackGoose opened this issue Feb 1, 2022 · 13 comments
Open

tailrec to enforce tail recursion #11

WhiteBlackGoose opened this issue Feb 1, 2022 · 13 comments
Labels
Language idea One single idea which may be a start of a design document
Milestone

Comments

@WhiteBlackGoose
Copy link
Member

WhiteBlackGoose commented Feb 1, 2022

If tail recursion is impossible, give an error.

Stolen from Kotlin.

@WhiteBlackGoose WhiteBlackGoose added the Language idea One single idea which may be a start of a design document label Feb 1, 2022
@LPeter1997
Copy link
Member

Agree, things like this are useful. One thing I'd like is to push these to metadata/attribute level and not have them occupy a keyword.

@aloisdg
Copy link

aloisdg commented Feb 9, 2022

F# has a rec keyword. Why not re-use it?

@LPeter1997
Copy link
Member

LPeter1997 commented Feb 9, 2022

@aloisdg The rec keyword in F# is essentially heritage from the ML family and denotes that a binding is recursive (can reference itself, like a recursive function). I'm not a fan of the feature, but that's a topic for another discussion. This feature is about telling the compiler that TCO can - and should - be applied to a function to avoid an expensive recursive call and an exploding call stack. This is why I'm not a fan of making it a keyword, as it's not modifying behavior/semantics and more like communication with the compiler that C# already does with attributes.

@WhiteBlackGoose
Copy link
Member Author

Yep, and F# doesn't have this feature :(

@WhiteBlackGoose WhiteBlackGoose added this to the 1.0 milestone Jun 2, 2022
@jl0pd
Copy link

jl0pd commented Jul 20, 2022

Making it an attribute will require allowing attributes on local functions, because tailrecs are rarely used as top level function, mostly they're inner functions. Take F# list module as an example (search for "loop"). Attributes on local functions will look too heavyweight

@LPeter1997
Copy link
Member

LPeter1997 commented Jul 20, 2022

How so? Allowing attributes everywhere seem simply reasonable.

func fact(n: int32): int32 {
    #[TailRecursive]
    func f(acc: int32, n: int32): int32 =
        if (n == 0) acc
        else f(n * acc, n - 1);
    return f(1, n);
}

Seems quite elegant IMO.

@333fred
Copy link
Contributor

333fred commented Jul 20, 2022

One thing to make sure of: iirc the CLR does not guarantee that it can always tailrec, even with the .tail directive. This lack of guarantee is one reason why C# does not have it.

@WhiteBlackGoose
Copy link
Member Author

F# does it on IL level, and that's how I want to have it too.

@333fred
Copy link
Contributor

333fred commented Jul 20, 2022

F# does it on IL level, and that's how I want to have it too.

And because the clr doesn't guarantee .tail is always respected, it can sometimes fail.

@WhiteBlackGoose
Copy link
Member Author

My point is that we don't need to emit IL's tail call at all. We can turn our recursion into a loop, basically, and only then compile it to IL

@333fred
Copy link
Contributor

333fred commented Jul 20, 2022

My point is that we don't need to emit IL's tail call at all. We can turn our recursion into a loop, basically, and only then compile it to IL

You can, but that (as far as I know) isn't how F# does it. It uses .tail.

@WhiteBlackGoose
Copy link
Member Author

WhiteBlackGoose commented Jul 20, 2022

It emits tail very rarely. Simple example for turning a rec into loop

That's exactly why unlike C#, it can almost guarantee TCO for tailrec.

@jl0pd
Copy link

jl0pd commented Jul 21, 2022

How so? Allowing attributes everywhere seem simply reasonable.

func fact(n: int32): int32 {
    #[TailRecursive]
    func f(acc: int32, n: int32): int32 =
        if (n == 0) acc
        else f(n * acc, n - 1);
    return f(1, n);
}

Seems quite elegant IMO.

Less elegant than F# version

let fact n =
    let rec loop acc x =
        if x = 0 
        then acc
        else loop (acc * x) (x - 1)
    loop 1 n

Making it an attribute will take same space, as required for entire function definition:

#[TailRecursive]
let loop acc x =    

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Language idea One single idea which may be a start of a design document
Projects
None yet
Development

No branches or pull requests

5 participants