[nem-en] Syntax change - your thoughts?

Gerard Murphy g.j.murphy at sageserpent.com
Tue Feb 7 15:34:57 CET 2006


Kamil,

> -----Original Message-----
> From: devel-en-bounces at nemerle.org [mailto:devel-en-bounces at nemerle.org]
> On Behalf Of Kamil Skalski
> Sent: 04 February 2006 12:25
> To: devel-en at nemerle.org
> Subject: Re: [nem-en] Syntax change - your thoughts?
> 
> > > There is one, though not very common reason, why plain [] causes
> problems.
> > > If it wouldn't collide with the rest of the language I would also like
> > > to use alwas [].
> >
> >
> > Thanks for that link; I've gone and looked at that page.
> >
> > However, what's being described there is exactly the problem I'd already
> > mentioned: because the implementation tries to avoid using semantic
> context
> > while parsing, it needs the dot character to avoid an ambiguity with
> indexer
> > calls (and as Alejandro points out, list comprehensions too).
> >
> 
> No, the problem lays a little futher than avoiding to use semantic
> context during parsing. It lays in "for every moment in existance of
> world" ambiguity of what actually
> 
> x [y] ();
> 
> means.
> 
> Let's assume following code:
> 
> namespace Root {
> class x [T] {
>   public this () { ... }
> }
> class y {}
> 
> class Foo {
>   public x [a : int] : void -> void
>   { get { ... } }
> 
>   y : int;
> 
>   foo () : void
>   {
>     _ = x [y] ();
>   }
> }
> }
> 
> Now, 'x [y] ()'  has two ambiguous interpretations:
>   Root.x. [Root.y] ();
> and
>   this.x [this.y] ();
> 


Perhaps you are misdiagnosing the problem here. I agree that the above
example leads to an ambiguity, but if I try the same thing using a
non-generic class to supply the constructor, and a non-generic method to
compete against the constructor, then I still have an ambiguity:-

    public
    class NotGeneric
    {
        public
        this()
        {
        }
    }
    
    
    public
    class AClient
    {
        public
        NotGeneric(): string
        {
            "Hmmm"
        }
        
        public
        Foo(): void
        {
            def something = NotGeneric(); // Oops!
            
        }
    }

I've tried this on the Nemerle 0.9.1 build and the compiler advises me
that:-

"typing fails on ambiguity between overloads".

Now, this does makes sense to me: I notice that the corresponding situation
in C# doesn't occur because of the use of the 'new' keyword to mark a
constructor call, so in Nemerle constructor calls and 'ordinary' function
calls compete in the type inference / overload resolution process.

I guess that the ambiguity in the non-generic case is a reasonable (perhaps
desirable?) consequence of the decision to avoid the 'new' keyword: so why
not define the generic case to have the same ambiguous behaviour in the
example you posted above?

At least you would have consistency between the non-generic and generic
cases, which makes it easier for novices like me to comprehend.

However, it might be the case that this behaviour has changed under the
latest release of the compiler, in which case I am (as ever) a dolt. :-)


> It is true that this case is rather rare and we have discussed
> ignoring it before. But it came out that in general people prefer the
> dot notation to make things less ambiguous and cleaner to read.
> 
> Ok, we could switch to plan [] syntax, but it would need an agreement
> in most of the community. And for example we (compiler developer) are
> quite happy with current solution - as I said before, this syntax is
> used very rarely and if it actually is, it's nice to have it explicit.
> 
> >
> > One situation is where a generic method (i.e. a method that is generic
> in
> > its right, not simply a method within a generic class) has type
> parameters
> > that are unrelated to its arguments:-
> >
> > lotsOfThings = SomeClass.createLotsOfThingsAndDoSomePostprocessing
> >                                         .[Thing](numberOfThingsDesired);
> >
> > Type inference isn't applicable here.
> 
> Of course there are examples where type inference is not applicable,
> but probably this is not the one of such cases.
> Did you try ommiting .[Thing] part in your program? Because compiler
> can infer the type not only from arguments passed, but also from the
> return value - if you ever used 'lotsOfThings' in some context
> requiring it to contain Thing, then you don't need to specify
> .[Thing].


I shouldn't have been in such a hurry when I posted my previous E-mail:
there are examples where the subsequent usage of 'lotsOfThings' would still
not be enough for type inference to pin down the type parameter in the call
to 'SomeClass.createLotsOfThingsAndDoSomePostprocessing()'.

Essentially, the problem is that the subsequent usage may be some code that
hands off 'lostOfThings' to a non-local piece of code, perhaps a legacy
system, that is only interested in the overall supertype of 'Thing': so the
type signatures of that code say nothing about specific kinds of 'Thing'.

In this sort of situation, type inference could correctly assign a return
type of 'List[WhateverThingSupertypeIsReferredToInLegacyAPI]' to the return
value of the factory function, but could say nothing about what type to
actually use to create the things.

Yes, I actually write code like that sometimes!

>>> Hushed, appalled silence follows from newsgroup readers. <<<

> 
> For example in my application, I have a auxiliary method, which takes
> SQL query and returns list of values (assuming that query returns
> single column):
> 
> public ['a] getSingleColumn (conn : IDbConnection, query : string) : List
> ['a]
> {
>   ...
> }
> 
> 
> and I use it simply as:
> 
> 
> def res = getSingleColumn (main_conn, "SELECT age FROM user");
> def my_age = res [0]  / 2;
> def date = getSingleColumn (main_conn, "SELECT born FROM user");
> def time_span = System.DateTime.Now - date [0];
> 

An excellent example to be sure, but this works precisely because local
usage pins the type down. What I'm talking about above is where type
information is partially lost over a system boundary - specifically, where I
am using inclusion polymorphism over some supertype in system A, and systems
B, C and so on need to supply objects to A.

I can't necessarily redesign A to use parametric polymorphism, because A
might have been written by a third party, or perhaps I want to mix and match
different kinds of objects within A at runtime, so I have to go the
inclusion polymorphism route.


> 
> The only situation when .[T] is needed is when you found some
> bug/missing feature in type inference engine, where is fails in some
> very complex expression:
> 
> fun (x) { foo (fun (y) { generic_method_returning_T () }) (x) } ....
> 
> Once or twice I encountered that compiler could not handle my code,
> though in general it is  a bug and should be reported.
> 
> The other situation is when generic parameter is totally independent
> from arguments and return value:
> 
> public print ['a] () : void
> {
>   System.Console.WriteLine (typeof ('a));
> }
> 
> but such cases are not "real-life" and very uncommon.


I'm not quite so sure about that: I'm thinking of the 'self registering
factory' idiom in C++, where a class or function template uses its type
parameter to cause the registration of a factory function for creation of an
object of the type parameter.

In this case, one doesn't see either a return value or an argument of the
type parameter - the type parameter is used entirely within the registration
code within the factory function.

Still, there are ways of refactoring this to make the type parameter
inferable, but I'm not sure the results are pleasant to look at.


> > As I mentioned before, I can make the effort in my own time to look at
> the
> > compiler sources to see how difficult it would be to make this syntax
> change
> > without breaking the compiler.
> >
> > I've already started looking at the compiler sources, but I thought it
> might
> > save time if I discussed the matter on the mailing list first.
> >
> > Needless to say, even if I could make the changes easily, I would
> obviously
> > hold back from submitting patches if the user community is against this
> > syntax change.
> >
> >
> 
> Of course this is a right thing to do. In general because of points
> above I would prefer even completely eliminating the generic specifier
> syntax, though it would limit language in cases like this 'print'
> example and workarounding type inference weaknesses.

I agree entirely - I would much rather have type inference by itself, but
there seem to be a set of cases that necessitate explicit use of the generic
specifier syntax.


> 
> If you have some nice solution for the problems posed in
> http://nemerle.org/Generic_specifier
> then we should consider it for implementation. :)


Drat - I'd hoped by now I would have persuaded the development team to do
this for me at no cost to myself. ;-)

OK, putting myself in your shoes, I would probably not want to expend time
and effort into doing this: it sounds like it's only me that runs into these
more obscure corners of the language.

Still, I'll carry on my investigations into seeing if the change could be
made in theory, at least for a week or two. If I get anywhere in that time,
I'll be back in touch, otherwise take it that I've quietly admitted defeat!

At least I'll learn a bit more about the language and the compiler, no bad
thing in itself.

Many thanks to you and Alejandro for your interest.

Cheers,

Gerard







More information about the devel-en mailing list