November 14, 2006
"Daniel Keep" <daniel.keep.lists@gmail.com> wrote in message news:ej6g8l$2g20$1@digitaldaemon.com...

>
> What I would prefer is if you introduced a new keyword that meant
> "declare". Just that. auto is currently used for type inference not
> because it MEANS "infer the type" but because it's the default storage
> class.
> ...
> Let's say the keyword is chosen to be "def" (being short for "define").


I think this is a good suggestion. Although "infer" is good because it says exactly what it's doing, it just sounds a bit odd for a keyword.  I like "def" .

I agree with the notion that there should be two different keywords. I think "auto" isn't the most descriptive keyword because, as Ary Manzana pointed out, "automatic what?". Using "scope" or "local" for RIAA is probable better for clarity. But if it had to stay as "auto", it wouldn't be the end of the world, either. :)

Regarding the original post:

Walter Bright wrote:
> The auto storage class currently is a little fuzzy in meaning, it can mean "infer the type" and/or "destruct at end of scope". The latter only has meaning for class objects, so let's look at the syntax. There are 4 cases:
>
> class Class { }
>
> 1) auto c = new Class();
> 2) auto Class c = new Class();
> 3) auto c = some_expression();
> 4) auto Class c = some_expression();
>
> The ambiguity can be resolved by saying that if auto is used for type
> inference, i.e. cases (1) and (3), then it does not mean RAII. If it is
> not used for type inference, i.e. cases (2) and (4), then it does mean
> RAII.

I'm not sure people would like having to remember this rule every time they tried to figure out which variables will have their destructors called. Why should explicitly declaring the type of a variable have anything to do with whether it's destructor is called? If I decide to modify my code to "turn on" type inference, I might inadvertently "turn off" the destructor call. Perhaps this came about in an effort to conserve keywords, which is a good goal. That's partly why I asked in my other post if type inference is important: because most people believe it should require another keyword. If you intend to avoid adding another keyword, then perhaps it would be better better for type inference be left out rather than to have the "auto" keyword overloaded as above. (Not that I'm opposed to type inference; I'm not.)

Jim


November 14, 2006
Walter Bright wrote:
> Lionello Lunesu wrote:
>> Confusing.. I thought const in D meant really 'constant'. If a const in a function can have different values across calls, then I suppose a const for a class can have different value for different instances too?
> 
> Yes.
Where is this documented? I've tried finding it in the spec but to no luck. I think a section going into details on all storage classes would be a good thing!

The main problem I have with the D spec is that a lot of stuff is very spread out, and finding the place where the detail you're looking for is, is often pretty hard.
November 14, 2006
Boris Kolar wrote:
> Proposed extensions, that should solve most RAII (and other) problems:
> 
> 1 Structs
> =========
> 
> 
> Structs are a lot like classes, except they can NOT:
> - have virtual methods
> - override inherited methods
> (technically: they can't have VMT table)

While in a certain sense true... I'd really like to avoid the C++ situation where the only difference is where they get allocated.  I know that's not quite what you're trying for here, but one thing could lead to another... that said -- continue;

> 
> 1.1 Immutable structs
> ---------------------
> 
> In addition to normal structs, immutable structs can be defined with:
> 
>   const struct Point {
>     int x;
>     int y;
>   }

Not a bad idea, although one could already accomplish this (I think) just by declaring the variable as const, rather than the type... which is notably more consistant.

> They can be constructed as:
> 
>   void test() {
>     Point p = Point(10, 20); // x = 10, y = 20
>     Point p = Point(x: 10, y: 20); // same as above
>     p.x = 30; // COMPILER ERROR: Point is immutable
>   }

If we can get static struct initializers into the open the same way we've gotten array literals out of static array initializers, then this wouldn't be needed.  Instead we could just:

# Point p1 = {10, 20} ;
# Point p2 = {x:10, y:20} ;

Already works, so long as p is declared static/const.

> 
> 1.2 Structs can have constructor(s) and destructor
> --------------------------------------------------
> 
> Also, structs may have constructor(s), destructor:
> 
>   struct File {
>     this() {
>       printf("File.this");
>     }
>     this(char[] name) {
>       handle = openFile(name);
>       // handle can be modified only in constructor
>     }
>     ~this() {
>       closeFile(handle);
>     }
>     HANDLE handle;
>   }

I'm still undecided on this, to be honest.  I don't think its a bad thing, per se, and certainly know of uses for it... but it makes me antsy for some reason.  May just be unreasonable paranoia.

> Invocation is deterministic (at the beginning and end of variable scope):
> 
>   void test() {
>     File f; // prints "File.this" (default constructor is invoked)
>     // destructor of f is called here
>   }

As it likely should be.

> 
> 1.3 Inheritance and implicit conversions of structs
> ---------------------------------------------------
> 
> Structs can inherit from one or more other structs:
> 
>   struct A {
>     int a;
>   }
>   struct B {
>     int b;
>   }
>   struct C: A, B {
>     // inherited: int a;
>     // inherited: int b;
>     int c;
>   }

This I would like to see.

> Structs (and classes too) can define implicit conversions:
> 
>   // A may be struct, interface, class, ...
>   struct D: A {
>     // nothing is inherited because implicit conversion is defined
>     this.A {
>       return A(a: 10);
>     }
>   }

If nothing else, it is an interesting syntax.  It might make a nice alternative to the opCast() method we have now.  Might even make opCast() obsolete (especially since opCast() can only provide conversion to a single type).

> Inheritance and implicit conversion test:
> 
>   void test() {
>     C c;
>     B b = c; // allowed
>     D d;
>     A a = d; // allowed
>   }

I assume that, given 'struct D: A' then the following:
# D d     ; // default ctor
# A a = d ; // allowed; implicit conversion
#   d = a ; // error: cannot convert an A into a D.

Or in other words, upon conversion, it actually /is/ an A, and no longer a D.  Otherwise, this could potentially negate some uses of structs as direct maps into data files.

> 
> 2 Classes
> =========
> 
> 
> 2.1 Explicit implementation of interfaces
> -----------------------------------------
> 
> Classes can explicitly implement interface methods or whole interfaces:
> 
>   interface IFoo {
>     void foo();
>   }
>   interface IBar {
>     void bar();
>   }
>   class Foo: IFoo, IBar {
>     // explicitly implement IFoo interface methods
>     void IFoo.foo() {
>     }
>     // explicitly implement interface IBar by delegation
>     this.IBar {
>       return getBar();
>     }
>   }

This I like.  :)

> 
> 2.2 Value classes
> -----------------
> 
> Value classes are like ordinary classes, except:
> - only constructor or destructor can change class state
> - they can not have null value (at least default constructor is called)
> - they have deterministic finalization if they implement 'Local'
> 
> Example:
> 
>   const class Foo: Local {
>     this() {
>       foo = 5; // foo can be modified (only) in constructor/destructor
>     }
>     ~this() {
>       printf("Foo.~this");
>     }
>     int foo;
>   }
>   void test() {
>     Foo foo; // foo.foo = 5 (default constructor is invoked, foo can't be null)
>     // prints "Foo.~this" and destroys foo here
>   }
> 
> Value classes can easily support deterministic finalization, because no cycles are
> possible (without hacking, anyway) and simple reference counting is sufficient.

Hm.  No opinion.

> 
> 2.3 Custom allocators
> ---------------------
> 
> Custom allocators allow any allocation strategy:
> 
>   // in Phobos
>   interface Allocator {
>     void* allocate(Type type);
>     void deallocate(Type type, void* mem);
>   }
>   // test application
>   void test() {
>     Foo foo = new(myAllocator) Foo();
>     // if Foo is value class and implements 'Local', then foo is deleted here
>     // ... else foo is deleted when myAllocator is deleted
>   }

Not a bad idea.  Not sure if I would use it, personally, but not a bad idea.

> 
> 3 Summary
> =========
> 
> 
> It seems that scoped variables are unnecessary, because value classes that
> implement 'Local' interface, together with implicit conversions, are sufficient
> in all of the following scenarios:
> 
> 1. Finalization is required:
> 
>   const class File: Local {
>     ~this() {
>       close;
>     }
>   }

Sure.  Currently achieved using 'auto'... no need to go into that mess all over again, of course.

> 2. Garbage collection must be avoided:
> 
>   const class Scoped(T): T, Local {
>     this(T object) {
>       _object = object;
>     }
>     this.T {
>       return _object;
>     }
>     private T _object;
>   }
>   Scoped(T) scoped(T)(T object) {
>     return new Scoped!(T)(object);
>   }
>   class Foo {
>     void foo() { ... }
>   }
>   void test() {
>     Foo foo = scoped(new Foo());
>     foo.foo();
>     // foo is deleted here :)
>   }
>   void test2() {
>     scoped(new Foo()).foo();
>     // Foo instance is deleted here too! :D
>   }

I'm not so sure I understand how this is "avoiding" GC... it seems to be /enforcing/ it. But its still a fascinating design concept.

> 3. Value class semantics is desired:
> 
>   const class Point {
>     int x;
>     int y;
>   }

If I want value semantics, I'll generally use a struct.  Just can't think of cases right off hand where I'd want this.

> Another (minor) suggestion is allowing method declarations without '()':
> 
>   class Foo: Bar {
>     int foo {
>       // no ambiguity here, consistent with 'this.Bar' definition
>       return 5;
>     }
>     this.Bar {
>       return bar;
>     }
>     private Bar bar;
>   }

Eh, maybe.  I would probably still always type them in there.  They don't take up much space, and I'm just too used to a pair of ()'s meaning, "Hey this is a function/method!" Also, it makes me think of C# style properties -- and while I would like to see them in D, I don't need constant false hope.  ;)

> Also, get rid of static opCall

Just: no.  Leave it be.

-- Chris Nicholson-Sauls
November 14, 2006
Tomas Lindquist Olsen wrote:
> 
> The main problem I have with the D spec is that a lot of stuff is very spread out, and finding the place where the detail you're looking for is, is often pretty hard.

Agreed.  I recommend using the comment page [click the button on the upper right of the spec page] to add cross references to things, and other findings about things that aren't clearly documented.

For instance on the operator overload page it doesn't mention opApply, which I think is the natural place you'd go to look for it.  But I added a link to where it can be found (statements, under 'foreach') on the comment page.

In general I think people should make more use of those comment pages. It's a very nice thing that Walter has added those links to every page of the spec.

--bb
November 15, 2006
Boris Kolar wrote:
> == Quote from Sean Kelly (sean@f4.ca)'s article

> I like the above suggestions. After thinking about it, I'm beginning to wonder if
> scoped variables are usefull at all. The way I see it, there are three cases,
> where one might desire scoped variables:
> 
> 1. Guaranteed, deterministic finalization (mostly disposal of system resources).
> For example, file should be closed when going out of scope. In this case, it would
> be better to make File class scoped, not individual variables:
>     auto/scoped/whatever class File {
>         this() { ... }
>         ~this() { close; }
>     }

Wouldn't you like to be able to return a file object from a function? You wouldn't be able to if all File objects were destroyed as soon as the went out of scope.

Anyway, thinking about it, "scoped" is kind of a limited concept.  Take one variable in one scope and destroy it as soon as that scope ends. You can't return something like that, you can't really meaningfully assign it to other things that way.

A more general concept would be to have something that is guaranteed to run its destructor as soon as there are no outstanding references. That's actually a lot simpler to do than general garbage collection. You just count references.  It's exactly what boost::shared_ptr does.

GC covers *most* cases where people use shared_ptr in C++ -- and in a much cleaner and potentially more efficient way.  But the cases where you want something cleaned up as soon as not in use are not handled so well by GC.  This includes "scoped" as one particular case.

So what about "counted" instead of "scoped"?

I guess this has the same issues as scoped (because scoped is basically just a binary refcount).  Namely, do you attach it to the class or to the instance?

In any event I think boost::shared_ptr / weak_ptr type functionality would be useful in D, but I don't see how to implement it cleanly as a library without an opAssign to keep track of the reference.

--bb
November 15, 2006
Kristian Kilpi wrote:
> On Sun, 12 Nov 2006 20:59:05 +0200, Walter Bright <newshound@digitalmars.com> wrote:
> 
> The following (shorthand) notation won't have that problem:
> 
>   Class c();

Does D have the same extern declaration ambiguity as C++?
In C++ some compilers at least interpret that to be an extern declaration for a function 'c' that returns a "Class" and takes no parameters.  So instead you have to say

   Class c;


I don't know if D suffers from this particular ambiguity or not.

--bb
November 15, 2006
Walter Bright wrote:
>>> The auto storage class currently is a little fuzzy in meaning, it can mean "infer the type" and/or "destruct at end of scope". The latter only has meaning for class objects, so let's look at the syntax. There are 4 cases:
>>>

Derek Parnell wrote:
>> Type inference and RAII are two distinct, unrelated and orthogonal
>> concepts. Using one keyword in subtly different syntax permutations to
>> indicate both concepts is just plain stupid.

Bruno Medeiros wrote:
> I *fully* agree. It comes a few days late, but I still wanted to say it. The irony and mischeviousness of the statement "The auto storage class currently is a *little fuzzy* in meaning" is teeth-grinding(or something). Walter, I hope that by judging the response in this thread you've seen that everyone agrees that these two language concepts should be well separated. There is really no discussion on that. The question to discuss is rather *how* that separations should be (which keywords, etc.).

I fully agree that they should be made more clearly distinct in the code.

But over the past few days I've also come to understand that there is some logic to the status quo.  They aren't totally orthogonal concepts when considered in the context of a simple value-type automatic variable.

'auto' for type inference is really just 'auto' for declaring an automatic variable, and the type inference is due to lack of specific type.

'auto' for RAII can be thought of as saying "this thing acts like an automatic variable" in that it's cleaned up just a regular value type would be.  int foo = expr(); leaves no traces behind after it's out of scope (because it's an automatic variable).  The expression myint foo = expr(); looks like it might also leave no traces behind, but that wouldn't be the case if myint were a Class.  So in that case  auto myint foo = expr() is saying "Really I mean it! Treat this like a plain-old value-type automatic variable, no matter what it is -- class, struct, or typedef."

That said, even though there is a certain kind of logic behind it, it would still be better for the people who have to *write* and *maintain* the code if the distinction were made a little more obvious.

--bb
November 15, 2006
Boris Kolar wrote:
> Proposed extensions, that should solve most RAII (and other) problems:

Heh heh.  That's a mighty big claim.  :-)

> 1 Structs
> =========
> 
> 
> Structs are a lot like classes, except they can NOT:
> - have virtual methods
> - override inherited methods
> (technically: they can't have VMT table)
> 
> 
> 1.1 Immutable structs
> ---------------------
> 
> In addition to normal structs, immutable structs can be defined with:
> 
>   const struct Point {
>     int x;
>     int y;
>   }
> 
> They can be constructed as:
> 
>   void test() {
>     Point p = Point(10, 20); // x = 10, y = 20
>     Point p = Point(x: 10, y: 20); // same as above
>     p.x = 30; // COMPILER ERROR: Point is immutable
>   }

I don't see much point in that really.
hahahah get it?  Not much Point.  yuk yuk yuk yuk yuk yuk.
I'll be here all week.

But seriously const applied to instances seems sufficient to me.  Why restrict the utility of the class in that way?  At least you need to find a better example than Point to illustrate your case.

> 1.2 Structs can have constructor(s) and destructor
> --------------------------------------------------
> 
> Also, structs may have constructor(s), destructor:

I'd like this too -- at least constructors.  But Walter is against destructors since (IIUC) it creates complications when passing structs as arguments or when they get created as implicit temporaries by the compiler.  Something like that.

Anyway, people use static opCall as a constructor now, but it's always annoying to write such a beast (or such beasts since you often write 3 or 4 different versions):

    static MyStruct opCall(S s, T t, R r) {
        MyStruct c; with (c) {
           m_svar = s;
           m_tvar = t;
           m_rvar = r;
        } return c;
    }

That's about as minimal as it can be, but it's still two superfluous lines and a superfluous return type that you wouldn't need in a real constructor.  And it may be slightly less efficient if the compiler doesn't optimize assignment well.  It could mean a create & copy vs just one in-place creation.  And it doesn't declare your intent clearly. It's just a convention.  static opCall doesn't necessarily return a new instance of the struct.

However, if you add constructors to structs what's the syntax for use going to be?  Personally I can't think of anything more natural than MyStruct(a,b,c).    So I think it would have to act just like a static opCall.  The only difference is you'd be able to write this instead of the above with all it's extra cruft:

    this(S s, T t, R r) {
           m_svar = s;
           m_tvar = t;
           m_rvar = r;
    }

Destructors I don't know about.  The main use case I have is little sentinal structs that do "set_state" in the constructor "reset_state" in the destructor.  (A lot of use for that in OpenGL programs).

The thing is I think that paradigm is not so clear.  The "fire-and-forget" nature of it is useful when programming

{
   auto st = scopedTransform(trans);
   ...
   {
      auto sc = scopedColor(color);
      ...
   }
}

but it's easy for those ... parts to get very long, and it's easy for those scopeTransform thingy's to get buried somewhere in the middle of the scope.  And then it's easy for someone to come along and see what looks like a superfluous block and decide to delete the extra {}, creating a hard to find bug.

So I actually think something like this may be a better style for D:

  auto st = tempTransform(trans); scope(exit) st.restore;

At least then the maintenance programmer can see clearly that there's a scope exit in the block and thereby know not to delete that extra {}. Plus that's currently valid D code.

>   struct File {

Something like a File shouldn't be a struct.  If you're going to be doing file IO, then probably the speed hit of having to do one dynamic allocation is not important.


> 1.3 Inheritance and implicit conversions of structs
> ---------------------------------------------------
> 
> Structs can inherit from one or more other structs:
> 
>   struct A {
>     int a;
>   }
>   struct B {
>     int b;
>   }
>   struct C: A, B {
>     // inherited: int a;
>     // inherited: int b;
>     int c;
>   }

Yeh, this has been suggested before.  There was a discussion about it fairly recently.  I don't see why this shouldn't be possible.


> Structs (and classes too) can define implicit conversions:
> 
>   // A may be struct, interface, class, ...
>   struct D: A {
>     // nothing is inherited because implicit conversion is defined
>     this.A {
>       return A(a: 10);
>     }
>   }

Don't know about that.  I think implicit conversion would only make sense for pointers to structs.  But maybe I'm missing your point.

> 2 Classes
> =========
> 
> 
> 2.1 Explicit implementation of interfaces
> -----------------------------------------
> 
> Classes can explicitly implement interface methods or whole interfaces:
> 
>   interface IFoo {
>     void foo();
>   }
>   interface IBar {
>     void bar();
>   }
>   class Foo: IFoo, IBar {
>     // explicitly implement IFoo interface methods
>     void IFoo.foo() {
>     }
>     // explicitly implement interface IBar by delegation
>     this.IBar {
>       return getBar();
>     }
>   }

So this is just an alternative to 'override'?  If so I like it.  I find I always end up putting a comments in my code anyway like:

  /// These implement the IImplementable interface
  override int foo() {...}
  override int bar() {...}

But if I move around methods those comments can get stale.

  int IImplementable.foo() { ... }
  int IImplementable.bar() { ... }

would be clearer and eliminate the need for override in most cases. Problem is I think (even though rare) an override can override a method declared in multiple different interfaces.


> 2.2 Value classes
> -----------------
> 
> Value classes are like ordinary classes, except:
> - only constructor or destructor can change class state
> - they can not have null value (at least default constructor is called)
> - they have deterministic finalization if they implement 'Local'
> 
> Example:
> 
>   const class Foo: Local {
>     this() {
>       foo = 5; // foo can be modified (only) in constructor/destructor
>     }
>     ~this() {
>       printf("Foo.~this");
>     }
>     int foo;
>   }
>   void test() {
>     Foo foo; // foo.foo = 5 (default constructor is invoked, foo can't be null)
>     // prints "Foo.~this" and destroys foo here
>   }
> 
> Value classes can easily support deterministic finalization, because no cycles are
> possible (without hacking, anyway) and simple reference counting is sufficient.

This is just different syntax for "auto class Foo" as far as I can tell.

> 2.3 Custom allocators
> ---------------------
> 
> Custom allocators allow any allocation strategy:
> 
>   // in Phobos
>   interface Allocator {
>     void* allocate(Type type);
>     void deallocate(Type type, void* mem);
>   }
>   // test application
>   void test() {
>     Foo foo = new(myAllocator) Foo();
>     // if Foo is value class and implements 'Local', then foo is deleted here
>     // ... else foo is deleted when myAllocator is deleted
>   }

I thought this was possible already.


> Another (minor) suggestion is allowing method declarations without '()':

Eh, I don't that as much of a gain, and it would make grepping for function definitions a little bit harder.

--bb
November 15, 2006
Chris Nicholson-Sauls wrote:
>>
>> Structs are a lot like classes, except they can NOT:
>> - have virtual methods
>> - override inherited methods
>> (technically: they can't have VMT table)
> 
> While in a certain sense true... I'd really like to avoid the C++ situation where the only difference is where they get allocated. 

The only difference between struct and class in C++ is the default protection level.  In a struct memebers are public by default, for classes they're private by default.  That's the only difference.  And also the inheritance in structs is public by default, and private for classes.

And that's why it's really annoying that you can't generically forward declare a class/struct without knowing which it is.

  ---foo.h----
  class ForwardDeclared;
  class Foo {
     ForwardDeclared *f;
  };
  ------------

This fails if ForwardDeclared turns out to be a struct.  Grrr.  Who cares which it is?!  They're the same freaking thing!  All you need to know, mr. dumb compiler, is that I've got a pointer, period.

(Maybe typename fixes this?  Does anyone know?).

--bb
November 15, 2006
== Quote from Bill Baxter (dnewsgroup@billbaxter.com)'s article
(immutable structs)
> But seriously const applied to instances seems sufficient to me.  Why restrict the utility of the class in that way?  At least you need to find a better example than Point to illustrate your case.

Small utility structs should usually be immutable - it's simply a matter of good design. A few seconds on Google gives enough reasons for immutable structs/classes: http://www-128.ibm.com/developerworks/java/library/j-jtp02183.html

> I'd like this too -- at least constructors.  But Walter is against destructors since (IIUC) it creates complications when passing structs as arguments or when they get created as implicit temporaries by the compiler.  Something like that.

C++ has no problems with destructors, therefore it should be possible to implement them in D as well.

> However, if you add constructors to structs what's the syntax for use going to be?  Personally I can't think of anything more natural than MyStruct(a,b,c).    So I think it would have to act just like a static opCall.

Yes, exactly what I had in mind.

> Something like a File shouldn't be a struct.  If you're going to be doing file IO, then probably the speed hit of having to do one dynamic allocation is not important.

Of course. But this was not meant to be a real world example. Basically,
I don't see much difference between structs and classes except that
structs don't have VMT table. Not having VMT table can be (sometimes) usefull,
especially if your struct mirrors layout of some other structure. And it
makes them a bit "safer", because you can't accidently override struct's
methods (which reminds me of another wish: compiler should at least warn when
you override methods without explicit 'override').

> Don't know about that.  I think implicit conversion would only make sense for pointers to structs.  But maybe I'm missing your point.

Implicit conversions make just as much sense as inheritance. Of course, indirect
implicit conversions can be ambigous, but there are algorithms to detect this. For
example, consider implicit conversions of "big" versions of numbers:
  BigCardinal -> BigInteger -> BigRational

A relatively simple rule about implicit conversion should be:
- implicit conversion is possible if the shortest conversion path to desired
type is not ambigous

> So this is just an alternative to 'override'?  If so I like it.  I find I always end up putting a comments in my code anyway like:

Explicit implementation is more than just 'override'. You can, for example, implement two different interfaces with the "same" method, like that:

  interface Foo {
    int foo();
  }
  interface Bar {
    char[] foo();
  }
  class FooBar: Foo, Bar {
    int Foo.foo() { ... }
    char[] Bar.foo() { ... }
  }

Also, you can implement all methods at once with delegation:

  class Foo: Bar {
    this.Bar { // will implement all Bar's methods
      return getBar();
    }
  }

> > Value classes can easily support deterministic finalization, because no cycles are possible (without hacking, anyway) and simple reference counting is sufficient.
> This is just different syntax for "auto class Foo" as far as I can tell.

Additionally, const classes are immutable, so no cyclic references are possible,
therefore
implementing deterministic finalization is much easier (for non-const classes,
compiler
would have to do escape analysis, which I suspect is not an easy task).

> > 2.3 Custom allocators
> > ...
> >     Foo foo = new(myAllocator) Foo();
> I thought this was possible already.

Yes, but you have to encode allocation strategy in class. So, in this case, Foo must
redefine the 'new' operator. My suggestion was that allocation strategy can be
redefined
for all (including existing) classes.

> > Another (minor) suggestion is allowing method declarations without '()':
> Eh, I don't that as much of a gain, and it would make grepping for function definitions a little bit harder.

Yes, not much gain. Since you can already omit '()' everywhere except in declarations,
I thought it would be more consistent to simply state that all '()' are optional.