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

Syntax for access modifiers #64

Closed
medvednikov opened this issue Apr 4, 2019 · 45 comments
Closed

Syntax for access modifiers #64

medvednikov opened this issue Apr 4, 2019 · 45 comments
Labels
Bug This tag is applied to issues which reports bugs.

Comments

@medvednikov
Copy link
Member

medvednikov commented Apr 4, 2019

Here's what I currently have in mind:

module foo

struct Foo {
  private_field  int // can only be accessed in the same module (default)
  len-           int // read-only field that can be accessed from an outside module, but not modified
  public_field+  int // public field that can be accessed and modified from outside
}


module main

fn main() {
  foo := new_foo()
  foo.private_field // will not compile
  println(foo.len) // OK
  foo.len++ // will not compile
  foo.public_field++ //OK
}

What do you think?

 

edit

Slight improvement:

struct Foo {
    private_field int // can only be accessed in the same module (default)
   -len           int // read-only field that can be accessed from an outside module, but not modified
   +public_field  int // public field that can be accessed and modified from outside
}

 

edit 2

Immutable struct fields by default got a lot of support:

struct Foo {
    foo1     int // can only be accessed in the same module, but not modified (default)
    foo2 mut int // can only be accessed and modified in the same module
   +foo3     int // read-only field that can be accessed from an outside module, but not modified
   +foo4 mut int // public field that can be accessed and modified from an outside module
}
@medvednikov
Copy link
Member Author

Perhaps formatting should be different:

struct Foo {
  private_field  int 
  len           -int
  public_field  +int 
}

@whoizit
Copy link
Contributor

whoizit commented Apr 4, 2019

most readable for me

struct Foo {
  private_field  int 
  -len           int
  +public_field  int 
}

@medvednikov
Copy link
Member Author

Yes, perhaps.

Functions:

fn +foo()

fn foo+()

@whoizit
Copy link
Contributor

whoizit commented Apr 5, 2019

same with functions
when there are dozens or hundreds of them with names of various lengths, it will be difficult for you to look for postfixes. Should be prefixed, Immediately visible.

@shouji-kazuo
Copy link

most readable for me

struct Foo {
  private_field  int 
  -len           int
  +public_field  int 
}

I agree with @whoizit suggestion.
more readable for me, since it should be clear that access modifier is related to field name, not to field type.

@medvednikov
Copy link
Member Author

Good point, let's do it this way.

@HedariKun
Copy link

why not using keywords?

@medvednikov
Copy link
Member Author

medvednikov commented Apr 5, 2019

why not using keywords?

I like the approach of Go and Oberon to achieve this without keywords.

Adding more keywords increases complexity of the language, and the definitions would look a lot more verbose:

struct Foo {
  private_field int 
  readonly len  int
  public public_field  int 
}

@HedariKun
Copy link

oh, that makes sense

@PavelVozenilek
Copy link

It would be also handy to allow minus as word separator in names. It is more readable than underscore.

-len would mean read only
some-name would be one symbol
-3 would be negative number
x - y would be expression, spaces around the minus required

@medvednikov
Copy link
Member Author

Yes, that would be nice, and I had precisely the same idea. Unfortunately it's not possible.

I don't want to make a whitespace sensitive language, and I want it to be similar to C/C++ and Go. This would be too big of a change for all these developers.

@PavelVozenilek
Copy link

PavelVozenilek commented Apr 5, 2019

All these languages suffer of TooLongNamesDisease. Especially C is asking for it. Since it looks that V won't allow function overloading, the pressure would be here too.

Words separated by dash are bit more readable than with underscore. (I noticed this in Lisp code.) Traditional typography employs en dash (Austria-Hungary), underscore is a hack from age of typewriters.

If arithmetic operators always require spaces around, then it won't be confusing for the reader. There's low chance of accidentally joining two words and making valid symbol, which then even compiles.

@ntrel
Copy link
Contributor

ntrel commented Apr 6, 2019

Since it looks that V won't allow function overloading

I assumed that, but I would like V to allow overloading only on number of arguments. This is a lot simpler than C++ overloading, no complications about fn f(int) vs fn f(i8) etc.

Adding more keywords increases complexity of the language, and the definitions would look a lot more verbose

If we're using symbols for this, I think the same syntax for public and private should apply to functions and types. The default could be public, which for fields means read-only [outside the struct module] (to follow your design). Then -identifier makes a symbol private. The special case would be +field, which means read/write public field:

fn public_func(){}
fn -private_func(){}
struct -private_type{}

struct public_type {
  read_only_public_field T
  -private_field T
  +read_write_public_field T
}

@medvednikov
Copy link
Member Author

Immutable struct fields by default got a lot of support:

struct Foo {
    foo1     int // can only be accessed in the same module, but not modified (default)
    foo2 mut int // can only be accessed and modified in the same module
   +foo3     int // read-only field that can be accessed from an outside module, but not modified
   +foo4 mut int // public field that can be accessed and modified from an outside module
}

@ntrel
Copy link
Contributor

ntrel commented Apr 6, 2019

Immutable struct fields by default got a lot of support:

Yes, where I wrote T, you can write e.g. mut int. My comment here is about access only, mutability is a separate concern. In your reply it seems there's no way to have a field be mutable in the current module, but read-only outside it. Read-only public fields seems a good default for fields that are mutable in private. Also, what about functions and type access as I mentioned?

@medvednikov
Copy link
Member Author

medvednikov commented Apr 6, 2019

In your reply it seems there's no way to have a field be mutable in the current module, but read-only outside it.

Good point, this is the 5th option, and it's not handled.

So perhaps Oberon's readonly - can be brought back for this:

struct Foo {
    foo1     int // can only be accessed in the same module, but not modified (default)
    foo2 mut int // can only be accessed and modified in the same module
   +foo3     int // read-only field that can be accessed from an outside module, but not modified   
   +foo4 mut int // public field that can be accessed and modified from an outside module

   -foo4     int // read-only field that can be accessed from an outside module and modified from an inside module
}

This is getting a bit complicated...

The default could be public

The default is private in all major languages, it's not going to change. The default must always be private.

@medvednikov
Copy link
Member Author

I assumed that, but I would like V to allow overloading only on number of arguments.

There will never be function overloading:

https://vlang.io/docs#fns

@PavelVozenilek
Copy link

There will never be function overloading

It is straying off topic, but:

There are valid reasons why function overloading got a really bad name:

  1. C implicit number/pointer conversion rules.
  2. C++ made the situation much worse by cast operators, two implicit conversions rule, and above all, by the namespace mess with Koenig lookup.
  3. Primitive C tools like grep and ctags cannot handle it.

If these mistakes are avoided, function overloading wouldn't be seen as a monster.

There are natural uses of overloading - math functions like sin/cos/etc. Even C realized it and attempted to add it into the language with C11. Unfortunately, they had chosen _Generic.

Function name overloading has one not so obvious advantage - it reduces the pressure to use unreadable LongNamesWhichAreFiresureUnique.


One also could take inspiration in Smalltalk, and use function parameter names as part of name resolution.

fn coordinates( x, y) // handles cartesian coordinates
fn coordinates(angle, len) // handles polar coordinates

result = coordinates(x: 10, y:20); // it is absolutely clear which overload is wanted here

@ntrel
Copy link
Contributor

ntrel commented Apr 10, 2019

The default must always be private.

OK.

-foo4 int // read-only field that can be accessed from an outside module and modified from an inside module

Read-only field syntax without mutable data should be an error, it's correct if mut int is the field type. I don't think your example is that complicated, when thinking of mutability and private/public as two separate concepts.

But should functions and type names be public or private by default? If private then +identifier would be a consistent way to make them public. If public, I don't think we can have -private_function_name as well as -read_only_public_field, it would be confusing.

@mindplay-dk
Copy link
Contributor

The plus and minus look like operators.

I find this extremely confusing and noisy - the meaning of plus and minus aren't naturally understood as anything other than operators, so this is a bit too creative for my taste.

Fewer characters do not necessarily make something easier to parse for a person.

We're used to words like "public", "private", "internal" etc. from english language - and if not from there, then from other programming languages.

It seems like you're struggling to make everything as few characters as possible?

I think that's misguided.

In my opinion, readability of code is much more important than how many characters you have to type - simply having having characters does not automatically make a syntax more readable. Think regular expressions. Most people needs weeks to months to learn the basics. Whereas most people learn to read languages like Java (while it does tend to be overly verbose) almost immediately - even if they don't learn the meaning of the keywords, anyone can immediately identify the keywords, which gives them at least a way to automatically associate terminology with keywords, so they can ask questions, do a search or talk to more senior coworkers about them.

Compounding everything down to symbols (worse, ambiguous symbols) increases the density of the code, which isn't automatically a good thing. Keywords aren't just noise - when used properly, they reduce the density and add to the semantics of the language.

Be careful not to compress the syntax to the point of obfuscating.

@jimutt
Copy link

jimutt commented Apr 24, 2019

I think the concerns of @mindplay-dk are valid and need to be considered. Though I personally actually prefer a symbol based system as it reduces the verbosity by a quite large amount compared to "private, internal, public" keywords. I also think it's important to remember that whereas fewer numbers of character doesn't, on its own, make for more readable syntax the opposite is also true. Otherwise we'd all be coding in COBOL-like languages.

I suspect that it's quite hard to know for sure which syntax would be more intuitive. While I get the idea of the regex comparison I don't think it's a very good comparison in this case. I'd personally like to argue that if you're a beginner it's not very clear what you can do to a "protected internal" property in C#. You need to know more than the dictionary definition of the actual words. And therefore I'm really not convinced that it's much harder to instead learn the contextual meaning of two or three different symbols. But that's just my personal thoughts, not backed by any reliable sources at all.

@mindplay-dk
Copy link
Contributor

Though I personally actually prefer a symbol based system as it reduces the verbosity by a quite large amount compared to "private, internal, public" keywords.

I agree, it does reduce verbosity.

But it does so at the expense of readability due to increased visual ambiguity - words are unambiguously recognized by the majority of the population, possibly with the exception of dyslexics; most of whom do learn to read today, and/or can memorize the spelling of a few keywords. Even non-english speakers who know the latin alphabet learn to read and write keywords.

Learning the meaning of arbitrary symbols is harder - especially when those symbols are ambiguous.

Being able to visually scan and read code is extremely important.

While verbosity is of course also a concern, you shouldn't barrel down on that problem, or any single problem, at the expense of everything else. Good language design is about balancing trade-offs. Are you sure that's what you're doing here?

It seems to me like you're asking a lot of new users - be careful not to compress and obfuscate the syntax to the point that too many will just look at this and dismiss it outright. Most newcomers will already know another language, and will recognize common keywords with a common meaning from most other mainstream languages.

I'm all for trying new things, but in my opinion you're trying to innovate something that doesn't really need innovation, and making some detrimental trade-offs in the process.

@han2015
Copy link

han2015 commented Apr 25, 2019

struct Foo {
private_field int
-len int
+public_field int
}

sorry, are you seriously!!? what the 'hell' syntax?

The plus and minus look like operators.

Agree with this, but i'm not the big fan of keywords too, cause it's more verbosity. I wonder why nobody to recommend the design of Golang, capitalize the first letter or not.

@jimutt
Copy link

jimutt commented Apr 25, 2019

I agree about the problematic ambiguity that comes with using the proposed + and -. I also think that the proposed meaning of the minus sign is far from obvious. But I think it'll probably be hard to come up with other symbol based modifiers that will be better and more intuitive. Though I still think that using "public" and "private" keywords does not feel like a good fit with the general syntax of V. Then I agree with @han2015 that it's worth considering Go's naming convention, considering that V is already sharing many similarities with Go.

@ntrel
Copy link
Contributor

ntrel commented Apr 27, 2019

I wonder why nobody to recommend the design of Golang, capitalize the first letter or not

In generic code it's very helpful to have type names Capitalized and variables starting lowercase. Otherwise it takes longer to work out what's going on in the code. For this reason I don't want to see Capitalized function names. If we want to change the symbol identifier itself to signify access, I'd prefer _symbol to mean private. But using the identifier has an annoyance: having to update all uses of a private symbol when it's made public. That's straightforward with a good editor, but it also creates diff noise (possibly creating conflicts in another pull request, which can be more annoying).

I don't think traditional attributes are necessarily verbose: e.g. pub: applying to all following fields until overridden. priv can then override single fields, and vice versa.

fn +foo()

I missed this comment before, so with this syntax functions are private by default. With pub/priv it would be possible to have functions and types public by default, as well as fields private by default. That seems more like what is typically wanted in a module.

@mindplay-dk
Copy link
Contributor

If the concern is sheer verbosity, a few languages opt for sectioning instead of annotations - for example:

struct User {
  private {
    id int
  }

  private mutable {
    email string
  }

  public mutable {
    first_name string
    last_name string
  }
}

This is visually unambiguous, and non-repetitive when you have to list 10-20 public or private fields.

@medvednikov
Copy link
Member Author

Yes, this is an option. If we use C++ syntax and skip private since all fields are private by default, we get

struct User {
    id int
 
  mutable:
    email string
  
  public mutable:
    first_name string
    last_name string
}

it would be possible to have functions and types public by default, as well as fields private by default. That seems more like what is typically wanted in a module.

Everything is going to be private by default. It's simpler, and that's how it works in all other languages.

@mindplay-dk
Copy link
Contributor

@medvednikov that looks really nice and functional 👍

@medvednikov
Copy link
Member Author

Yes, I like it too, and I even prefer the full names public and mutable here :)

@medvednikov
Copy link
Member Author

mut indeed is a very ugly word. F# uses let mutable x = 1, maybe I should also replace mut x := 1 with mutable x := 1.

Most variables shouldn't be mutable anyway.

@medvednikov
Copy link
Member Author

Mutable version being more verbose might encourage people to make their variables immutable where possible.

@arhen
Copy link

arhen commented Apr 27, 2019

Yes, this is an option. If we use C++ syntax and skip private since all fields are private by default, we get

struct User {
    id int
 
  mutable:
    email string
  
  public mutable:
    first_name string
    last_name string
}

it would be possible to have functions and types public by default, as well as fields private by default. That seems more like what is typically wanted in a module.

Everything is going to be private by default. It's simpler, and that's how it works in all other languages.

Idk, but i didn't like this approach. too much things to typing with keyboard XD

@medvednikov
Copy link
Member Author

@arhen this version is not going to be a lot more verbose when you have many fields:

struct User {
    id int
 
  mutable:
    email string
  
  public mutable:
    first_name string
    last_name string
    field1, field2, field3, field4 string
}
struct User {
    id int

    email mut string
  
    +first_name mut string
    +last_name mut string
    +field1 mut string
    +field2 mut string
    +field3 mut string
    +field4 mut string
}

@andrewpillar
Copy link

Just my 2 cents, how about using _ prepended to a name for delineating whether or not something is private?

struct User {
    _private int
    public int
}

fn _private() {

}

@jimutt
Copy link

jimutt commented Apr 27, 2019

The sectioning example with private by default is by far my favourite this far, feels more clean and convenient than most other alternatives.

@whoizit
Copy link
Contributor

whoizit commented Apr 27, 2019

if we reduce function to fn, then we should reduce and public to pub and mutable to mut. But then it will be Rust)
I personally like fn, pub, mut; private and immutable by default. wordiness is enough in other languages, It should be compact.

@medvednikov
Copy link
Member Author

Yeah, I agree with this in the end.

struct User {
    id int
 
  mut:
    email string
  
  pub mut:
    first_name string
    last_name string
    field1, field2, field3, field4 string
}

@medvednikov
Copy link
Member Author

Just my 2 cents, how about using _ prepended to a name for delineating whether or not something is private?

This makes everything public by default, which is undesirable.

Also the majority of the module's code now has method calls starting with _.

@GordonGgx
Copy link

Yeah, I agree with this in the end.

struct User {
    id int
 
  mut:
    email string
  
  pub mut:
    first_name string
    last_name string
    field1, field2, field3, field4 string
}

I like it.

@mindplay-dk
Copy link
Contributor

mut indeed is a very ugly word. F# uses let mutable x = 1, maybe I should also replace mut x := 1 with mutable x := 1.

@medvednikov why did you go back on this in your last examples?

Please use proper words. pub mut is so much less readable than public mutable.

@whoizit
Copy link
Contributor

whoizit commented Apr 28, 2019

@mindplay-dk if it is a public mutable, you should write function instead of fn. But then I will personally teach Rust, instead vlang.
Maybe if you want to write "readable", then you should learn TypeScript or something like that? This is exactly what you want https://www.typescriptlang.org/docs/handbook/classes.html

@mindplay-dk
Copy link
Contributor

@whoizit "Go use another language" - really not constructive. See also this.

@medvednikov
Copy link
Member Author

@mindplay-dk I decided not to change anything for now. I realized I was spending too much time on this. I need to finish lots of projects before the end of April.

It will be trivial to change the syntax in the future with the help of vfmt, which is called on every run of the program.

@kdevb0x
Copy link

kdevb0x commented Jun 2, 2019

I have a small suggestion, if you should choose to go the symbol route. I know that there was some discussion about using '!' with generic types, but what about prefixing ann identifier with an exclamation for external visibility (exported)?

struct Foo {
  private int 
  !length int  // exported read-only
   mut !bar int  // exported and mutable 
}

But I rather prefer:

struct Foo {
  private int 
  length! int  // exported read-only
   mut bar! int  // exported and mutable 
}

This idea is derived from Dylan, where it is convention to end a binding identifier with '!' if it destructively modifies its arguments.

@medvednikov
Copy link
Member Author

The syntax has been decided. Thanks for your input.

https://vlang.io/docs#mod

larpon pushed a commit to larpon/v that referenced this issue Feb 24, 2022
@medvednikov medvednikov added the Bug This tag is applied to issues which reports bugs. label Jul 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug This tag is applied to issues which reports bugs.
Projects
None yet
Development

No branches or pull requests