Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change

Contexts and Perl 6

by John M. Dlugosz (Monsignor)
on May 18, 2009 at 17:39 UTC ( #764687=perlmeditation: print w/replies, xml ) Need Help??

I've been musing over "contexts". In Perl 5, we have scalar context, list context, and maybe one or two other obscure ones.

Perl 6 has lots. I'm musing on this question: Just what exactly is the concept of a context in Perl 6? It might be a lot cleaner and conceptually pure than what we have in Perl 5.

What it is, fundamentally, is information that propagates into functions, which can affect the behavior of the function if the code chooses to examine and act on it. In Perl 6, there are contextual (dynamically scoped) variables, so it's not unique that information should flow this way.

The reason a function cares is because it indicates what the caller will do with this information. Why return a list, laboriously gathered from a remote data base, if the caller is just going to render the list into a number, getting the count? Instead it can return the count directly.

So the context indicates how the caller will use the object. It appears already that contexts in Perl 6 are linked with types (or roles, read "abstract interfaces"). So, to what extent is context simply equal to the type the caller wants? Is there more to it than that?

Maybe the "more to it" is just the same thing going on in the parser. A language construct, like a function, can mean different things depending on the context.

So, should any type coercion be a context? That includes vacuous conversions. A function may have a declared return type, so is strongly typed. But the caller immediately coerces it to something else, either with syntax for that purpose, or implicitly by feeding it to another function or language construct that wants a particular type. That immediate coercion of the result, "you gave me T1, what I really wanted was T2" is the context, I think.

If functions are strongly typed, the caller knows he is getting back T1, rather than waiting to see what he got after it's been executed. Same difference. The body of the function is written with certain knowledge of returning a T1, but can check to ask, "what is the caller really wanting?" and obtain T2.

This means that "lots" of contexts, corresponding to the built-in main interface types that match various sigils, is an understatement. Every type can be what is wanted, in context. That makes the name, wanted, even more clear. Given that simple meaning, what else is needed? The parser has some awareness of the primitive contexts, too. That won't be affected by making any type appear in wanted. Likewise, the smart-match won't care that the actual wanted is a more concrete type than it's checking for.

This also opens up some new tricks, in that a detailed return type can be used to prepare the return value more concretely. That is, instead of returning a plain Array of Any, which needs to be checked in some detail before it can be assigned to the caller's Array of Car, the function can make the return value of the desired concrete type in the first place. This can go part way to addressing the container-of-subtypes problem.


Replies are listed 'Best First'.
Re: Contexts and Perl 6
by moritz (Cardinal) on May 18, 2009 at 17:53 UTC
    What it is, fundamentally, is information that propagates into functions, which can affect the behavior of the function if the code chooses to examine and act on it.

    In Perl 6 we're moving away from that notion, because multi dispatch doesn't allow that concept to work very well (and which is why I proposed to get rid of want(), which tells you that context).

    Consider for example

    multi sub a(Str $x) { ... } multi sub a(Bool $x) { ... } a(b()); # is b() here in Bool or in Str context?

    Instead contexts are common type coercions to which all (or nearly all) objects know how to respond. So when in Perl 5 a function would ask for its context, in Perl 6 it returns an object instead which behaves appropriately in all contexts.

      I see your point: MMD, moreso than ordinary virtual function dispatching, requires information flow outward, like we're used to in most other languages. That contradicts the idea of strong typing flowing inward.

      So if we do away with want, we just have the odd built-in parts left. The parser behaves differently in scalar or list context, like with Perl 5. No real need for others. Can we do away with it totally, leaving only a faint shadow of the concept as the way the non-context-free grammar works?

      I'm thinking, but haven't quite crystallized on it, that given an Array object, putting it in item context just returns the same object. But the situation is not the same! Or, did the surrounding code already decide what it's going to do with it (e.g. STORE it in an item container) before calling the Item coercion? I like that better, because objects don't behave differently based on "context".

      I do like the idea of a function being able to short-circuit its logic if it knows its being called a certain way. But I realized that this is just a poor-man's way to overload on return type.

      Thinking about its intended use, and modeling it as overloading, I'd say the selection would be ranked based on the way information is distilled from the main type. By default, native type > Positional > Numeric > Integral > Bool. If done as overloadings, then the function could declare the rankings relative to the others. If done using wanted, want would return a junction and the code could decide which was more important. Basically just order the when clauses in the desired order, and it won't care that the given is a junction.

      Other languages don't have a mechanism to make that efficient. But a Functional language or other pure design approach would have these very things: only one function to return the list, and the caller decides cardinality from that. You have functions for lists, and you don't need to "cross" all the list-related operations with all operations that return lists!

        The parser behaves differently in scalar or list context, like with Perl 5.
        Actually, no, unlike Perl 5 the Perl 6 parser has no clue about scalar vs list context, which is completely driven by run-time binding so that context can be lazy. The only place the parser appears to pay attention to this is in choosing how to parse assignment based on the form of the left side, but this is merely the application of a precedence limiter to the right side so that the operator precedence parser knows whether to stop at the next comma or not. The only semantic distinction that heavily influences parsing is whether a name is predeclared as a type or named value (in which case the parser will not look for function arguments), but this also is not related to run-time context.
      I think I see what you're hinting at in the reference post.

      Instead of stuffing the object with the various values for all contexts, the function just puts code in for each possible context, and only executes the one actually picked on.

      So the list-cardinality thing would return a lazy list that plugs back into the real code, and pre-populate the count. The function called doesn't contain the "real" code.

      I like that as it ties in with the lazy list concepts. Easy to do, using current specs (I think):

      sub arduous ($param -->@) { return @() ... { return reticent($param) } }
      That is, with an empty list on the left, it should use the iterator for all elements. In this case, it's not an iterator but the real function that generates all the elements (it might itself be implemented to return a lazy list). Cute syntax, but this doesn't pre-set the length. We should have an easy way of writing functions that do that: a function appears to return the list, but really just sets up a lazy list.

      sub arduous ($param -->@) { my ArduousIterator $it .= new($param); return $it; }
      Here, since ArduousIterator is derived from a friendly iterator creator class that does whatever the name of the standard iterator role is, it supports .lines which returns all the items, but can be lazy. And it has the @ contextualizer, which calls .lines. Since the function is defined to return a list, that is called automatically without worrying about what my caller is doing with it.

      The function would set up the length (or at least provide for it if anyone ever wants to know) and a function to generate elements.

      I like that. It handles the opposite case, where the length is difficult to compute and ends up just getting all the elements anyway. There, you are better off not asking for the length and just waiting for the end of the list to happen.

      If the construct that made the return list was itself a lazy list maker, such as map, you don't have to be so explicit about it. Once you introduce lazyness at the bottom layer, it should take care of itself.

      I do think, though, that hiding the different potential faces of the object down at this level reduces the ability to optimize. If you inline the call, for example, it's not at all clear which bits create the face you are interested in.


        Actually the current spec allows something along these lines:
        sub my_context_sensitive { return $someobject but { method Str { # code for computing the string representation } method list { # code that's called in list context } ... } }

        So the cost of that is just creating a closure for each "overloaded" context.

Re: Contexts and Perl 6
by JavaFan (Canon) on May 18, 2009 at 19:59 UTC
    What it is, fundamentally, is information that propagates into functions,
    Actually, it's information that propagates into terms. Which can be function calls, but in Perl5, you probably can see the most number of different possible contexts when the value of an object needs to be fetched: overload dispatch (boolean context, string context, numeric context, scalar deref context, array deref context, hash deref context, code deref context, typeglob deref context, iterator context). And beside the mentioned list/scalar/void context, Perl also has lvalue context.

    Note that not all contexts are mutually exclusive.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://764687]
Approved by moritz
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others chanting in the Monastery: (4)
As of 2018-06-18 00:46 GMT
Find Nodes?
    Voting Booth?
    Should cpanminus be part of the standard Perl release?

    Results (107 votes). Check out past polls.