Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
257 views
in Technique[技术] by (71.8m points)

scope - What's the difference between 'my' and 'our' in Raku?

Furthermore, what criteria should inform your use of one over the other?


I found a few SO answers/comments ([1][2][3]) that mostly answer the title's question, however I thought it nicer to have it in a single place. The question was inspired by a similar one in Perl.

question from:https://stackoverflow.com/questions/66047559/whats-the-difference-between-my-and-our-in-raku

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The my scope declarator implies lexical scoping: following its declaration, the symbol is visible to the code within the current set of curly braces. We thus tend to call the region within a pair of curly braces a "lexical scope". For example:

sub foo($p) {
    # say $var;       # Would be a compile time error, it's not declared yet
    my $var = 1;
    if $p {
        $var += 41;   # Inner scope, $var is visible
    }
    return $var;      # Same scope that it was declared in, $var is visible
}
# say $var;           # $var is no longer available, the scope ended

Since the variable's visibility is directly associated with its location in the code, lexical scope is really helpful in being able to reason about programs. This is true for:

  • The programmer (both for their own reasoning about the program, but also because more errors can be detected and reported when things have lexical scope)
  • The compiler (lexical scoping permits easier and better optimization)
  • Tools such as IDEs (analyzing and reasoning about things with lexical scope is vastly more tractable)

Early on in the design process of the language that would become Raku, subroutines did not default to having lexical scope (and had our scope like in Perl), however it was realized that lexical scope is a better default. Making subroutine calls always try to resolve a symbol with lexical scope meant it was possible to report undeclared subroutines at compile time. Furthermore, the set of symbols in lexical scope is fixed at compile time, and in the case of declarative constructs like subroutines, the routine is bound to that symbol in a readonly manner. This also allows things like compile-time resolution of multiple dispatch, compile-time argument checking, and so forth. It is likely that future versions of the Raku language will specify an increasing number of compile-time checks on lexically scoped program elements.

So if lexical scoping is so good, why does our (also known as package) scope exist? In short, because:

  1. Sometimes we want to share things more widely than within a given lexical scope. We could just declare everything lexical and then mark things we want to share with is export, but..
  2. Once we get to the point of using a lot of different libraries, having everything try to export things into the single lexical scope of the consumer would likely lead to a lot of conflicts

Packages allow namespacing of symbols. For example, if I want to use the Cro clients for both HTTP and WebSockets in the same code, I can happily use both, and refer to them as Cro::HTTP::Client and Cro::WebSocket::Client respectively.

Packages are introduced by package declarators, such as class, module, grammar, and (with caveats) role. An our declaration will make an installation in the enclosing package construct.

These packages ultimately exist within a top-level package named GLOBAL - which is fitting, since they are effectively globally visible. If we declare an our-scoped variable, it is thus a global variable (albeit hopefully a namespaced one), about which enough has been written that we know we should pause for thought and wonder if a global variable is the best API decision (because, ultimately, everything that ends up visible via GLOBAL is an API decision).

Where things do get a bit blurry, however, is that we can have lexical packages. These are packages that do not get installed in GLOBAL. I find these extremely useful when doing OO programming. For example, I might have:

# This class that ends up in GLOBAL...
class Cro::HTTP::Client {
    # Lexically scoped classes, which are marked `my` and thus hidden
    # implementation details. This means I can refactor them however I
    # want, and never have to worry about downstream fallout!
    my class HTTP1Pipeline {
        # Implementation...
    }
    my class HTTP2Pipeline {
        # Implementation...
    }

    # Implementation...
}

Lexical packages can also be nested and contain our-scoped variables, however don't end up being globally visible (unless we somehow choose to leak them out).

Different Raku program elements have been ascribed a default scope:

  • Subroutines default to lexical (my) scope
  • Methods default to has scope (only visible through a method dispatch)
  • Type (class, role, grammar, subset) and module declarations default to package (our) scope
  • Constants and enumerations default to package (our) scope

Effectively, things that are most often there to be shared default to package scope, and the rest do not. (Variables do force us to pick a scope explicitly, however the most common choice is also the shortest one to type.)

Personally, I'm hesitant to make a thing more visible than the language defaults, however I'll often make them less visible (for example, my on constants that are for internal use, and on classes that I'm using to structure implementation details). When I could do something by exposing an our-scoped variable in a globally visible package, I'll still often prefer to make it my-scoped and provide a sub (exported) or method (visible by virtue of being on a package-scoped class) to control access to it, to buy myself some flexibility in the future. I figure it's OK to make wrong choices now if I've given myself space to make them righter in the future without inconveniencing anyone. :-)

In summary:

  • Use my scope for everything that's an implementation detail
  • Also use my scope for things that you plan to export, but remember exporting puts symbols into the single lexical scope of the consumer and risks name clashes, so be thoughtful about exporting particularly generic names
  • Use our for things that are there to be shared, and when its desired to use namespacing to avoid clashes
  • The elements we'd most want to share default to our scope anyway, so explicitly writing our should give pause for thought

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...