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

Define properties using let public #811

Open
charlesroddie opened this issue Nov 26, 2019 · 7 comments
Open

Define properties using let public #811

charlesroddie opened this issue Nov 26, 2019 · 7 comments

Comments

@charlesroddie
Copy link

charlesroddie commented Nov 26, 2019

Define properties [edit: and public fields] using let public

An example class with current syntax:

type A() =
    let u = 0
    static let v = 1
    member _.U = u
    member t.W(i) = t.X + i + v
    member t.X(i) = if i = 0 then 1 else t.X(i-1) * v
    static member Y(i) = i + v

I propose we add let public syntax to expose properties. The class could then be rewritten as:

type A() =
    let public U = 0
    static let v = 1
    let rec public X(i) = if i = 0 then 1 else X(i-1) * v
    let public W(i) = X + i + v
    static let public Y(i) = i + v

Pros and Cons

Pros:

  1. a) The common pattern let u = ... followed by member _.U = u can be replaced by the single line let public u = ....
    b) The cumbersome _. unused bindings are not present. (They would also be removed if member P = expr #626 is implemented.)
  2. Sequential ordering with explicit recursion is enforced, as usual in F# code, with the usual benefit of being able to understand code by reading it in sequentially. (Note that the second version swaps W and X and is easier to understand as a result.)

Current F# class code is a tradeoff between 1. and 2. If you use more let u = ...; member _.U = u you get the benefits of code order at the expense of verbosity. This suggestion would remove that tradeoff.

Cons:

  1. The public API of a class can no longer be found by looking at the bottom of the class definition.
  2. It's another way of doing the same thing, and properties with setters would still need to be implemented with the current member... syntax.

Notes

let public U = f() would be equivalent to let u = f() followed by member _.U = u, so f() is only evaluated once.

There would be no way to replicate member _.SayHello = printf "hello world" using this syntax, although member _.SayHello() = printf "hello world" could be done as let public SayHello() = .... Arguably this is an advantage as the fact that properties without arguments execute their bodies whenever called is a fact that can surprise beginners and catch out experienced users.

Extra information

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

L

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • [ x ] This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • [ x ] I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • [ x ] 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:

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

Tarmil commented Dec 17, 2019

This creates a troubling inconsistency though: let x = 1 creates a field, but let public x = 1 creates a property.

@charlesroddie
Copy link
Author

charlesroddie commented Dec 28, 2019

Does that make an observable difference to anything @Tarmil or is this just an implementation detail? This issue could be changed to refer to public fields instead of properties if that gives greater consistency?

@smoothdeveloper
Copy link
Contributor

@charlesroddie, I'm personally liking the current dichotomy between let and member, it is all clear / abiding to principle of least astonishment, at least once you've defined enough custom types with F# and are familiar with order of definitions.

Regarding the pros:

  • 1.a, proposing to replace what is currently a one-liner with a single symbol on the right hand side with a new construct that seem to bring fair amount of ambiguity
  • 1.b, what about allowing to discard _. altogether? I'm not sure it has been proposed but it would feel much more natural and solve the concern of unecessary noise: member U = u
  • 2, it is still enforced when sticking to single symbol / expression on right hand side, those are just exposing the internal let bound symbols if sticking with this idiom.

The cons + my mitigation of the pros are making this suggestion a no go (as in no upvote from me).

@charlesroddie
Copy link
Author

In F# it's standard to have a linear structure where definitions depend only on previous definitions. Currently, because of the cumbersomeness of defining things and then creating members out of them, there is an incentive to use member t.SomeProperty = ... which can refer to other properties both above and below. So there is a linear structure of lets followed by some mutually recursive properties.

If removing _. is implemented, which is linked in the OP and I hope does happen, then there will be more incentive to create properties member SomeProperty = ... which cannot reference other properties. You could then ban usage of member t. in a codebase and you would be left with lets followed by properties which only depend on previous definitions and not on each other, making the whole structure linear. And I agree that would be a mitigation and a large improvement.

Relative to that situation, yes this suggestion would just avoid the need to write member U = u at the end of class definitions. So it would save a few lines of code. But it would also allow to define fewer things (just U instead of both U and u) and allow looking things up with a single "go to definition" instead of with two "go to definition"s.

I wanted to explore whether the language property of encouraging linear structures and concision which is present in the rest of the language could be maintained inside class definitions. I understand that this suggestion is a larger syntax change than the language would usually make.

@smoothdeveloper
Copy link
Contributor

@charlesroddie thanks for additional context, the actual core motivation wasn't clear to me, or rather, your additional comment makes it more explicit / helps with my reading of your suggestion. you'd like to enforce order of declaration to 100% of the code, while removing need to define the boiler plate members on custom types.

I think the idioms and design choice of having members being rec are kind of set in stone and resonate with most people having used F# for a while, the confusion it brings to have let become a property just on basis of visibility modifier is really going to be confusing, yet, another syntax choice to achieve your goal would be nice if it felt natural.

I personally see the F# enforced order of declaration as nice to have, especially with short let syntax, but I'm not taking it as the strongest aspect leading to correctness in custom type definitions.

If this is a really important concern, working on an analyzer/lint making sure that members are no more than a single short expression or even a sole symbol would get you there, albeit with the cons of the boilerplate oneliners which you highlight, but I think it is minimal burden.

I just upvoted #626 as I believe it is helping a bit while not breaking the strong dichotomy between let and member nor making visibility modifier do something else than what they are intended.

@charlesroddie
Copy link
Author

charlesroddie commented Jan 2, 2020

@Tarmil This creates a troubling inconsistency though: let x = 1 creates a field, but let public x = 1 creates a property.
@smoothdeveloper the confusion it brings to have let become a property just on basis of visibility modifier is really going to be confusing, yet, another syntax choice to achieve your goal would be nice if it felt natural.

On consideration, the implementation would be similar to member val. let x = 1 creates a private field, whereas let public x = 1 would create a backing field and expose it as a property, similar to member val x = 1.

Similarly let public Square(x:int) = x*x would create a backing field of type int -> int*int and expose it as a property.

The similarity to member val means that this suggestion could also be implemented by extending member val syntax:

  1. allowing it to be placed together with, rather than only at the end of, let and do bindings
  2. allowing member val Square(x:int) = x*x syntax

I think let (mutable) public is a more consistent syntax than member val (with get, set) but am open to other opinions: should this suggestion use let public syntax or member val?

@smoothdeveloper
Copy link
Contributor

@charlesroddie, regarding the semantics of creating a backing field exposed by a property, it indeed makes more sense to reuse the member val syntax.

Considering your main concern is enforced order of declaration, what about one or a mix of those alternative approaches:

  • an optional warning, that triggers if non reced members call into others that aren't defined first
  • a nonrec modifier at member level, turning the logic above in an error
  • a nonrec modifier at type level, turning the logic above in an error for all the members that aren't rec
  • considering nonrec type, allow those to have mixed order between let and member so you can achieve what you want without the oneliners, but at the same time, we don't change the (great) defaults where members need to appear after all let symbols.

nonrec might not be the right keyword.

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

4 participants