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

Single character class name results in syntax error #2063

Open
mzagozen opened this issue Jan 9, 2025 · 3 comments
Open

Single character class name results in syntax error #2063

mzagozen opened this issue Jan 9, 2025 · 3 comments
Labels
bug Something isn't working

Comments

@mzagozen
Copy link
Collaborator

mzagozen commented Jan 9, 2025

Acton Version

0.24.1.20250108.8.28.39

Steps to Reproduce and Observed Behavior

I would like to define a class named with a single character:

class Z:
    pass

The compiler seems to parse the single-character name as a type variable instead:

ERROR: Error when compiling z module: Syntax error

/home/mzagozen/aoc/2024/src/z.act:52:7:
   |
52 | class Z(value):
   |       ^
unexpected 'Z'
expecting name (not type variable)

Expected Behavior

The code compiles!

@mzagozen mzagozen added the bug Something isn't working label Jan 9, 2025
@nordlander
Copy link
Contributor

This is not a bug but a consequence of the need to syntactically separate type variable names from classes and protocols. Most languages that implement type inference have a similar rule, with varying flavors. (An extreme case is the language Miranda, whose type variables are limited to the set *, **, ***, ...)

The motivation comes from the clumsiness that an explicit scoping of type variables would lead to. As an example, consider the type signature of our built-in function map:

    map: ((A)->B, Iterable[A]) -> Iterator[B]

Its generic type can be read a simple pattern that stands for all the concrete instantiations the function actually supports, like

    map: ((int)->str, Iterable[int]) -> Iterator[str]
    map: (((str,float))->float, Iterable[(str,float)]) -> Iterator[float]
    map: ...

But this assumes we're allowed to quickly identify the placeholders (i.e., the type variables) in the generic pattern above without much analysis. A syntactic rule that says a single upper-case character is always a type variable allows us to do that. But if A and B could also potentially stand for something else the task would be much harder, as in

    class B(object):
        ....
    ....
    ....
    map: ((A)->B, Iterable[A]) -> Iterator[B]

Now the full set of names in the current scope would have to be scanned before we can tell what the map signature means.

Of course, if type variables were always explicitly bound one wouldn't have this problem. The signature

    map: [A,B] => ((A)->B, Iterable[A]) -> Iterator[B]

is independent of any other definitions of A and B in scope, but also a bit harder to read. It's a rather univocal experience in languages with type inference that allowing type variables to be implicitly bound greatly simplifies their use.

Moreover, if the syntactic separation of type variables were to be removed, one would also have to deal with signatures of the following shape:

    map: [car,bike] => ((car)->bike, Iterable[car]) -> Iterator[bike]

Not only are the type variable names extremely ill chosen here, but the mere fact that they are arbitrary names seems to suggest that they carry some other meaning than just being placeholders in a type pattern. Which they still are, even in a language that would allow such syntax.

So we have the syntactic separation of type variables for a reason, even though it outrules certain meaningful class names (yes, I've wanted to create small examples with classes C and D myself!). But the exact definition of how type variables are made distinct can always be debated, as it is only in type annotations these names play a role. Maybe single character class names could be tolerated if there just were a way to mark them as such in annotations? Perhaps

    myfun: (dict[A,class B]) -> dict[class B, A]

could be considered? Here we use a keyword do indicate that B should not be read as a type variable, which remains the default interpretation of A. Something to come back to later, I guess.

@plajjan
Copy link
Contributor

plajjan commented Jan 9, 2025

I think we should leave the rules as-is. Our current grammar simplifies the compiler. I think we should just document the naming rules so users aren't as surprised. Perhaps expand compiler error messages too...

@mzagozen
Copy link
Collaborator Author

mzagozen commented Jan 9, 2025

Thanks @nordlander for the explanation. In light of this it is clear that just using a slightly longer name for a class is a much simpler solution. But I agree, these rules should be documented in the book.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants