vlang / v

Simple, fast, safe, compiled language for developing maintainable software. Compiles itself in <1s with zero library dependencies. Supports automatic C => V translation. https://vlang.io
MIT License
35.64k stars 2.15k forks source link

Syntax for access modifiers #64

Closed medvednikov closed 5 years ago

medvednikov commented 5 years ago

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 commented 5 years ago

Perhaps formatting should be different:

struct Foo {
  private_field  int 
  len           -int
  public_field  +int 
}
whoizit commented 5 years ago

most readable for me

struct Foo {
  private_field  int 
  -len           int
  +public_field  int 
}
medvednikov commented 5 years ago

Yes, perhaps.

Functions:

fn +foo()

fn foo+()

whoizit commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

Good point, let's do it this way.

HedariKun commented 5 years ago

why not using keywords?

medvednikov commented 5 years ago

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 commented 5 years ago

oh, that makes sense

PavelVozenilek commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

@medvednikov that looks really nice and functional 👍

medvednikov commented 5 years ago

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

medvednikov commented 5 years ago

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 commented 5 years ago

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

arhen commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

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

whoizit commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

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

medvednikov commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

The syntax has been decided. Thanks for your input.

https://vlang.io/docs#mod