Access Control Syntax

34 todsacerdoti 20 5/26/2025, 9:07:31 PM journal.stuffwithstuff.com ↗

Comments (20)

Joker_vD · 2h ago
> I assumed that the module system mostly didn't interact with other language features, so I could kick it down the road for now.

The main other language feature it interacts with is types. And with compile-time type checking. Especially in the "separate compilation" scenario. Especially in the "incremental compilation" scenario. It was a hot research topic back in the 90s, culminating with SML's idea of translucent modules. It's still a research topic today.

Exporting/importing values and variables, on the other hand, is entirely straightforward in comparison. The only thing you really have to think about is whether "const" means "constant during one program execution" (i.e. whether doing "x := 3" is legal or not) or "constant during the whole development cycle" (i.e. whether you can inline it's current declared value into the places where it's used or not).

nigeltao · 15h ago
For Wuffs, top level declarations start with either pub or pri (and both keywords have the same width, in a monospace font).

    pub status "#blah"
    pub struct foo(etc etc)
    pri func foo.bar(etc etc)
Since code is also auto-formatted, you can do things like "show me a structural overview of a package's source code" with a simple grep:

    rg -N ^p   std/jpeg/*.wuffs
If you want just the exported API, change p to pub:

    rg -N ^pub std/jpeg/*.wuffs
mdaniel · 14h ago
The cited claim about python mangling dunders is 100% not my experience

> If a class member starts with two leading underscores, then it really is private. The language will name mangle it to make it inaccessible.

  $ cat > jimbo.py <<PY
  __to_whom__ = "world"
  def __hello():
      return __to_whom__
  PY
  python3 -c '
  import jimbo
  print(dir(jimbo))
  print(jimbo.__to_whom__)
  '
  ['__builtins__', '__cached__', '__doc__', '__file__', '__hello', '__loader__', '__name__', '__package__', '__spec__', '__to_whom__']
  world
and, just on the off chance they really meant "class member" I tried that, too

  $ cat jimbo.py; python3 -c 'import jimbo
  print(dir(jimbo.s))
  print(jimbo.s.__to_whom__)
  '
  class Sekrit:
    __to_whom__ = "world"
    def __hello(self):
        return self.__to_whom__
  s = Sekrit()

  ['_Sekrit__hello', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__to_whom__', '__weakref__']
  world
rnice · 14h ago
Only names which start with double underscores AND do not end with double underscores.
mdaniel · 14h ago
Doesn't the "__hello" def qualify for your description? It shows up in both dir() outputs, although admittedly it is "name mangled" with the outer class name, so maybe that's what they meant

I guess this could qualify for a TIL because I didn't know Python treated leading and trailing dunders any different and that they were its implementation of "protected" and "private" only by convention

arjvik · 12h ago
The name mangling is what is being referred to. Specifically, you can access it as Sekrit()._Sekrit__hello(), but that's a pretty obvious boundary-break compared to .__hello().

Members ending in a double underscore as well aren't mangled, because this interferes with syntax sugar methods like __add__ (addition operator overloading).

pdpi · 15h ago
Something the article mentions in passing is Java’s “package private” being the default.

For the longest time, I believed that this was dumb, and that the default should’ve been private. Over time, my style has changed a fair bit, and these days I tend to think of packages, rather than classes, as the more important unit of code, and I find that package private was probably the right choice.

jevndev · 16h ago
I’ve stumbled into this problem before while drafting a language I want to make*. A lot of the design philosophy is “symbols for language features” and as such import/export is handled by `<~`and `~>`. An example of an exported function:

``` <~ foo := (a: int) { a - 1 } ```

Then at the import site:

``` ~> foo ```

* some day it’ll totally for real make it off the page and into an interpreter I’m sure :,)

metayrnc · 17h ago
I like the final approach. What about

-def sayHi()

Or

def- sayHi()

I feel like having a minus communicates the intend of taking the declaration out of the public exports of a module.

amonks · 16h ago
There's some prior art here from Clojure, where defn- creates private definitions and defn public ones:

https://clojuredocs.org/clojure.core/defn-

In Clojure this isn't syntax per-se: defn- and defn are both normal identifiers and are defined in the standard library, but, still, I think it's useful precedent for helping us understand how other people have thought about the minus character.

munificent · 17h ago
That's clever way to think of "-". :) I'll think about that.
kzemek · 16h ago
It might be from me being so used to it, but I do like Elixir’s `def`/`defp` second best to Rust’s `pub`
nagaiaida · 15h ago
personally, i like that raku goes the other way, with exported bits of the interface explicitly tagged using `is export` (which also allows for the creation of selectably importable subsets of the module through keyed export/import with `is export(:batteries)`/`use TheModule :batteries`, e.g. for a more featureful interface with a cost not every user of the module wants to pay).

it feels more natural to me to explicitly manage what gets exported and how at a different level than the keyword used to define something. i don't dislike rust's solution per se, but if you're someone like me who still instinctually does start-of-line relative searches for definitions, suddenly `fn` and `pub fn` are separate namespaces (possibly without clear indication which has the definition i'm looking for)

lizmat · 7h ago
Actually, a module can implement any export heuristics by supplying an EXPORT subroutine, which takes positional arguments from the `use` statement, and is expected to return a Map with the items that should be exported. For example:

    sub EXPORT() { Map.new: "&frobnicate" => &sum }
would import the core's "sum" routine, but call it "frobnicate" in the imported scope.

Note that the EXPORT sub can also be a multi, if you'd like different behaviour for different arguments.

nagaiaida · 6h ago
neat! i've never needed more than i could get away with by just sneaking the base stuff into the mandatory exports and keying the rest off a single arg, but that'll be handy when i do.
trashburger · 15h ago
Put it in metadata. ;) Image-based languages can associate metadata with live objects, which is how stuff like category info and visibility is provided. It doesn't affect runtime, of course, but it can give you squiggly lines in the live environment editor.
aappleby · 15h ago
I'll add another option from my toy language experiments:

Names visible outside their scope begin with a dot.

  foo = {
    a = 3;
    .b = a + 1;
  };
  print(foo.a) // error
  print(foo.b) // '4'
zabzonk · 12h ago
If anyone can explain what he thinks C++ is doing in the "Modifiers section", I would be interested. As far as I can see, he doesn't understand C++ (or C). And why is "Eldritch" started with a capital?
listeria · 11h ago
he's talking about how in C++ you write the modifier once, and all subsequent declarations have the same access specifier, i.e:

  class A {
  public:
    int a;
    float b;
  private:
    ...
  };
zabzonk · 11h ago
Ok, thanks - but surely this is the most sensible and readable way of doing things? Otherwise, why not make every member begin with "class A"? There probably is some language that requires this, I guess :-) Isn't language design wonderful.