5
$\begingroup$

What would it take to design a language that permitted construct such as below?

int int = 10;

Such a language would need to be able to infer contextually whether a type name or an expression is expected. Are there any constructs in a C++ or Java style language that would be equally valid whether a type name or an expression is used in any particular place, making them truly ambiguous if the identifier was both defined as an expression and a type? How can they be worked around syntactically?

$\endgroup$

3 Answers 3

6
$\begingroup$

A number of existing languages do allow this and there is no particular issue with it. Java permits it for reference types, for example, as do C# and Scala:

Foo Bar = new Foo();
Bar Foo = new Bar();

This code is accepted, and works without issue and generally without ambiguity.

There is one ambiguous construct this creates in the Java model: ordinarily, Foo.meth() calls the static method meth on the Foo class, but now it will attempt to dispatch on the variable instead and either fail or find an unrelated method from type Bar. A different language could distinguish instance and static method access syntactically and not face this issue, or simply not have them, or not overload the name of the type with the name of the class.

Many languages have no meaningful dynamic existence of types (or associated entities) at all, and these would generally not have even the opportunity for a run-time issue to arise. These are not forced to disambiguate at all.

Strictly speaking, this is also doable in a number of functional languages with algebraic data types and first-class functions. For example, in the Haskell code

data Foo = Foo Int
x = Foo

the second Foo is in the value namespace, holding a function from Int to Foo. Both type and function do remain available in their contexts as usual. However, this variable can't have any other value than that, so it may not meet the spirit of the question.

There's then no particular semantic problem with allowing this, and certainly nothing that rules it out entirely.


As well as these semantic issues, there can be syntactic ones: C famously has context-dependent lexing due to typedefs already, and generally requires feeding names of declared types into the lexer so that it can disambiguate (A)*B as a cast or multiplication. Given int int = 10, (int)*x would be terminally ambiguous.

It would be possible to constrain the syntax rules so this can't be written, in this case for example by requiring that the pointer dereference be written (*x), but it is a deeper change. Similar issues might arise in other languages that would otherwise benefit from a clear distinction between type and variable names.


From a language-design perspective, having types (or classes, constructors, ...) as first-class values in a shared namespace is quite fundamental, and a language that did not allow it would be very different. This is a choice that the language is making, probably deliberately. For example, first-class values for these may be used for dependency injection, higher-order programming, or pattern matching, and core to the ergonomics of the language.


All in all, allowing this is within the range of choices available to the language designer. There are tradeoffs involved in that, and it may be incompatible with other, more important features, but certainly not ruled out entirely if different choices are made.

$\endgroup$
4
$\begingroup$

This is already possible in some popular programming languages: for example, Java and Typescript both allow this.

class Foo {
    static Foo Foo = new Foo();
}
type Foo = {foo: string}
const Foo: Foo = {foo: 'foo'};

So long as the type name is an identifier and not a keyword, it's sufficient to not have first-class types, so that the type name isn't also an expression which refers to the type object as a value. (Of course, if the name is a keyword then it can't also be an identifier for a variable name.)

Or even if you do have first-class types, this might still allowed when the variable is declared in a different scope, so that it shadows the binding of the name to the type object. This would probably be very limited though, since the expression new Foo() would probably then resolve to the local name rather than the type.

$\endgroup$
4
  • $\begingroup$ Even in some languages with first-class types, they have some extra syntax to make it unambiguous, like Foo.self in Swift or typeof(Foo) in C#. $\endgroup$
    – Bbrk24
    May 23, 2023 at 23:56
  • $\begingroup$ It is still a problem to access static methods or fields from the type in Java without being first-class. $\endgroup$
    – Michael Homer
    May 24, 2023 at 0:07
  • $\begingroup$ @MichaelHomer True. ((Foo) null).bar() would be available as a workaround for that where bar is a static method. (And it won't throw a null pointer exception, because Java gonna Java.) $\endgroup$
    – kaya3
    May 24, 2023 at 0:15
  • $\begingroup$ Oh, that's horrific. $\endgroup$
    – Michael Homer
    May 24, 2023 at 0:19
1
$\begingroup$

Function call or type cast?

One example of an ambiguous statement in a C-style language that supports type names and object names overlapping is if there is a function or function pointer called x and a type called x.

The expression (x)(10) would be ambiguous because there is no way of knowing just by looking if I am calling the x object with 10 as an argument, or if I am casting 10 to type x.

One way to disambiguate is to use an extra set of parenthesis around the x like ((x))(10). This would unambiguously refer to the function call because as there is no expression after (x) in this case so there is no cast.

Types cannot be 'parenthesized' by themselves by putting parenthesis around an ambiguous name so we are forcing the compiler to treat the name as an expression.

As such, in ambiguous cases, the type name can be the 'default.' And to opt into the expression version, we can place parenthesis around the type.

$\endgroup$
1
  • $\begingroup$ Then again, functions are usually called without the starting parentheses, so it would usually be safe for a compiler/interpreter/whatever to default to the latter case. $\endgroup$
    – Someone
    Nov 19, 2023 at 0:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .