Nim: Pure enums allow using the same name but allow nonqualification with quirky behaviour

Created on 18 Jun 2018  路  10Comments  路  Source: nim-lang/Nim

type
  X {.pure.} = enum
    red

  Y {.pure.} = enum
    red

echo red # red, with unused warning for Y
let newRed = red # this works too and can be operated on

when false: # undeclared identifier: 'red' for both,
  echo red.ord
  from typetraits import `$`
  echo red.type
Language design

Most helpful comment

There was a discussion not so long ago where @Araq argued that there is no issue in allowing non-qualified access to pure enums, as long as no ambiguity arises.

Hence the condition on pure enums was relaxed a bit. The problem here is that there is an ambiguity.

In my opinion, allowing only qualified access to pure enums is a simple and clear rule to explain and to follow, and I do not see much value in allowing non qualified access.

All 10 comments

I am not sure if this is about Language Design. To me this looks like a bug or a regression, since code from the manual does not work as described anymore:

type
  MyEnum {.pure.} = enum
    valueA, valueB, valueC, valueD

echo valueA

The manual states that this code should give an error because of the pure pragma, but it compiles and runs fine.

KR Axel

I agree this should be considered a bug, it defeats the whole purpose of using pure on an enum.

There was a discussion not so long ago where @Araq argued that there is no issue in allowing non-qualified access to pure enums, as long as no ambiguity arises.

Hence the condition on pure enums was relaxed a bit. The problem here is that there is an ambiguity.

In my opinion, allowing only qualified access to pure enums is a simple and clear rule to explain and to follow, and I do not see much value in allowing non qualified access.

Yes, you don't see much value. I've actually tried to use the old pure enums though and they are terrible. {Foo.valA, Foo.valB} violates DRY and so you need even more complex rules like "In a set construction, the prefix can be avoided. Oh and in case statements too. Oh and in array constructions..."

And then these rules fail to be applied in DSLs...

@Araq I actually don't have any problems in relaxing the conditions for pure enums, the current issue is just a bug that needs to be ironed out. I was just trying to reconstruct why things changed.

Even if I don't see much value in this particular circumstance, I am mostly agnostic about whatver choice gest implemented for pure enums

I'd also be alright with it after hearing the rationale, provided this bug gets fixed and the compiler gives useful error messages similar to other ambiguities.

Relevant discussion in 2017 from @dom96, @PMunch, @Araq and me https://irclogs.nim-lang.org/22-10-2017.html#13:54:23

| Time | Person | Text |
|----------|------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| 13:54:23 | dom96 | I don't get it. You don't have to prefix enum names even when {.pure.} is specified? |
| 14:03:38 | Araq | dom96, pure enum field names get their own scope that is queried if the name was not found in any other scope |
| 14:04:09 | Araq | so as long as nothing else clashes with it you can use these names without the Enum. prefix |
| 14:04:35 | dom96 | So now the only difference is that I cannot use a prefix for non-pure enums |
| 14:04:56 | Araq | er, sure you can |
| 14:04:56 | dom96 | This behaviour should be implemented for non-pure enums and pure enums should have been left alone |
| 14:05:19 | dom96 | so what's the difference between pure/non-pure enums now? |
| 14:05:21 | Araq | well the point is to remove the whole distinction between the two enum types |
| 14:05:57 | dom96 | er, so you've basically removed {.pure.}? |
| 14:07:09 | FromGitter | \https://nim-by-example.github.io/types/enums/ |
| 14:07:27 | PMunch | Pure was nice though.. |
| 14:07:44 | dom96 | It is nice. |
| 14:07:48 | Araq | I unified the two concepts, not sure why you don't understand it |
| 14:07:57 | dom96 | Which is why I'm trying to understand what the purpose of Araq's changes is |
| 14:08:09 | FromGitter | \ | 14:08:09 | Araq | "pure was nice though", omg you can still prefix them all you want |
| 14:08:23 | PMunch | But you can't force people to.. |
| 14:08:27 | dom96 | indeed |
| 14:08:32 | PMunch | I thought that was the entire point of pure |
| 14:08:39 | dom96 | precisely |
| 14:08:43 | Araq | the point was to prevent clashes |
| 14:08:50 | PMunch | To say: this symbol doesn't make any sense without the enum name |
| 14:09:10 | dom96 | But you've just said that you can prefix non-pure enums too |
| 14:09:21 | dom96 | so if there are clashes you can prevent them |
| 14:09:31 | PMunch | And what happens if you have "OneEnum.test" and "AnotherEnum.test", both marked as pure. What will test refer to? |
| 14:09:38 | FromGitter | \this:this.} pragma |
| 14:09:56 | Araq | it never was about enforcing rules, it was about preventing clashes |
| 14:10:14 | Araq | nobody wants these rules anyway: |
| 14:10:17 | Araq | case e |
| 14:10:24 | Araq | of SomeEnum.valueA: |
| 14:10:28 | Araq | of SomeEnum.valueB: |
| 14:10:38 | dom96 | I do |
| 14:10:44 | Araq | of SomeEnum.valueC: echo "just kill me already" |
| 14:10:57 | dom96 | Sometimes it makes sense to have a short enum value name |
| 14:11:04 | dom96 | and then a qualifying prefix that is long |
| 14:11:19 | dom96 | In that case I want to force the user of my library to prefix the enum |
| 14:11:46 | Araq | yeah, that is exactly what Nim is NOT about |
| 14:11:55 | Araq | your users are adults. |
| 14:13:22 | Araq | if foo in {SomeEnum.short, shortToo, shorter} |
| 14:13:38 | Araq | # ^ context is everything |
| 14:16:27 | PMunch | Hmm, I still feel this should be handled somehow else.. |
| 14:16:51 | dom96 | okay, then we should get the ability to enforce this when importing enums |
| 14:17:36 | dom96 | btw my book mentions the pure pragma |
| 14:18:19 | PMunch | TBH, that shouldn't be a factor in the language design |
| 14:18:30 | PMunch | Of course it's not good, but such is life.. |
| 14:19:22 | dom96 | True. |
| 14:20:01 | Araq | people argued that every enum should be pure and then we should have special rules in 'case' and set literals to be able to avoid the prefix |
| 14:21:06 | PMunch | That makes more sense.. |
| 14:21:13 | Araq | but this doesn't work well, what about [value: "a", valueB: "b"] array constructions |
| 14:22:51 | PMunch | "SomeEnum.[value: val1, valueB: val2]" for "type SomeEnum = enum val1, val2" |
| 14:23:05 | PMunch | Same for set |
| 14:23:49 | PMunch | Case is the only special thing |
| 14:24:37 | Araq | that's not true at all |
| 14:25:41 | Araq | in Karax for example the problem comes up all the time too, we want a CSS "enum" without the prefixes that yet doesn't clash |
| 14:26:11 | Araq | registerEvent(onclick, proc ...) |
| 14:26:28 | Araq | # ok, onclick is nothing unexpected here |
| 14:26:55 | Araq | registerEvent(Event.onclick, proc ...) # some other onclick clashed so I need to write it out |
| 14:27:23 | dom96 | It's hard to tell that onclick is an enum type |
| 14:27:33 | dom96 | it could be a procedure |
| 14:27:41 | Araq | it's superior to your "I want to enforce it everywhere until I figured out that's a horrible idea" |
| 14:27:43 | dom96 | forcing the prefix makes it clearer |
| 14:27:49 | dom96 | It makes the code far more readable |
| 14:27:54 | PMunch | Agreed |
| 14:28:03 | Araq | no, I disagree and Nim doesn't work this way |
| 14:28:20 | miran | prefix sometimes makes things clearer |
| 14:28:33 | Araq | everything else in the language doesn't work this way either anyway |
| 14:28:41 | dom96 | But sadly this indeed isn't how Nim was designed |
| 14:29:37 | Araq | Event.onclick # you don't know anything about enums here, could also be ModuleName.onclick |
| 14:29:38 | PMunch | Fair enough |

I still think if the pragma is here to stay, {.pure.} should be renamed {.qualified.}, and {.qualified.} should always require the enum prefix. {.pure.} is not really descriptive of what the pragma does. (And maybe we should have import qualified Foo instead of from Foo import nil)

But, I think this is unnecessary policing by the compiler, assuming this is an internal enum, you're free to use your preferred style (always qualifying, or qualifying when necessary), if it's a public module, don't force your style on other projects.

All in all I think this should be handled in a Nim linter, with options chosen by the library authors, not in the compiler.

Just an opinion,
I like that you don't to prefix everything with MyEnum. it saves a lot of strokes. I think just error message needs to be improved for example above. Nim should say that red is ambiguous.

I think the focus was mishandled in this issue, it's also a matter of not having to deal with hungarian notation or the full name of the enum in the enum member like SDL_SCANCODE_UNKNOWN instead of Scancode.Unknown. To better express my point:

type StreetLight {.pure.} = enum
  red, yellow, green

let foo = StreetLight.red

instead of

type StreetLight = enum
  stlRed, stlYellow, stlGreen

let foo = stlRed
Was this page helpful?
0 / 5 - 0 ratings