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

Attributes for local lets #848

Closed
5 tasks done
Happypig375 opened this issue Mar 10, 2020 · 7 comments
Closed
5 tasks done

Attributes for local lets #848

Happypig375 opened this issue Mar 10, 2020 · 7 comments

Comments

@Happypig375
Copy link
Contributor

Attributes for local lets

I propose we allow attributes for local lets.

The existing way of approaching this problem in F# is to put the lets that need attributes at the top level, polluting Intellisense.

module M =
    let [<System.Diagnostics.DebuggerStepThrough>] private f _ = ()
    let [<Literal>] private Constant = 3
    let f x =
        // FS0842: This attribute is not valid for use on this language element
        let [<Literal>] Constant = 3
        // FS0842: This attribute is not valid for use on this language element
        let [<System.Diagnostics.DebuggerStepThrough>] f _ = ()
        ()

Pros and Cons

The advantages of making this adjustment to F# are

  1. Independence between proper scoping and attribute utilization
  2. Less Intellisense pollution
  3. Conciseness by not needing to pass local values into local functions just because they need attributes

The disadvantages of making this adjustment to F# are none that I can think of.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): S

Related suggestions: None

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I or my company would be willing to help implement and/or test this
@Happypig375
Copy link
Contributor Author

C# already has this: dotnet/roslyn#38801

@abelbraaksma
Copy link
Member

I think this is great to have, especially for literals, but I doubt it's going to be easy. Some things come to mind:

  • often, attributes are used with discovery of methods that need to get special treatment, but in F# there's no one to one relation between the name and the attribute. We'd need some way to get the original name as well.
  • the location of the definition of the final let binding function is a class, not a method. That class has one or more Invoke methods. Would we append the attributes to these methods?
  • currently, attributes are not maintained when a method uses curryable arguments. It's very common to curry in F#. Would we append the attributes to each intermediate function?
  • local functions are often inlined, there's no way to maintain the attributes in such case. Should we raise an error, or fix that in some other way?
  • what happens with quotations?

That's from the top of my head. There may be a lot of subtleties involved here. Alternatively, we could only allow a subset of attributes that are recognized by F# or by the CLR.

@abelbraaksma
Copy link
Member

Can we combine this with #984? I think both have merit, but need some ironing out

@dsyme
Copy link
Collaborator

dsyme commented Apr 13, 2023

This doesn't seem right for F#.

The expression language has always been kept "simple" in the sense that it's largely just plain and simple functional programming with various additions to augment the power of FP. There's never been any real intention that it should be possible to reflect over the internals of compiled expressions. This also allows the compiler great leeway to perform optimziations.

This philosophy seems stable and reasonable, and it needs some really big and important reason to allow the .NET-style attributions and other such reflective things to intrude into the expression language.

Since I'm spring-cleaning I'll close this out for these reasons.

@dsyme dsyme closed this as completed Apr 13, 2023
@Bananas-Are-Yellow
Copy link

Here is an example of why this is useful. I want to do the following so that the compiler will confirm that the inner function loop is indeed tail-recursive.

let factorial n =
    [<TailCall>]
    let rec loop n acc = 
        match n with
        | 0 | 1 -> acc
        | _ -> loop (n - 1) (n * acc)
    loop n 1

If I put [<TailCall>] on the outer function, only the body of the outer function is checked, not the code inside the inner function.

Yes, I could make loop a private function of the module, but since it is only ever intended to be used by factorial it is natural to make it a nested function. And if I have other loop functions at module scope, I'd have to give them all unique names.

@Happypig375
Copy link
Contributor Author

Happypig375 commented Jan 17, 2024

@dsyme Attributes are not only for reflecting at runtime but also for various compile-time items like [<Constant>], [<TailCall>] and [<Struct>] (for active patterns). Would a whitelist be considered to only allow compile-time attributes? Or warn non-whitelisted attributes.

@Thorium
Copy link

Thorium commented Jul 16, 2024

This works:

let myState = 5
let inline processFive ([<InlineIfLambda>] simpleOp) =
    simpleOp myState

let test =
    processFive (fun c -> c + 1) +
    processFive (fun c -> c - 1)

The same code inside a function doesn't work:

let myCodeContext() = 
    let myState = 5
    // error FS0824: Attributes are not permitted on 'let' bindings in expressions
    let inline processFive ([<InlineIfLambda>] simpleOp) =
        simpleOp myState

    let test =
        processFive (fun c -> c + 1) +
        processFive (fun c -> c - 1)
    test

...so I have to cut the processFive out of the myCodeContext to somewhere else. Which is a bit annoying as then it leaks the "internal" myState out of myContext.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants