in reply to Contexts and Perl 6

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.

Replies are listed 'Best First'.
Re^2: Contexts and Perl 6
by John M. Dlugosz (Monsignor) on May 18, 2009 at 18:37 UTC
    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.
        Very interesting, thanks.

        I see in the Synopses where context behavior is being replaced by typed objects; e.g. "undef in scalar context or () in list context" replaced by "Nil".

        So, is there anything left of the concept of context flowing inward in the manner of a contextual variable?

        —John

Re^2: Contexts and Perl 6
by John M. Dlugosz (Monsignor) on May 18, 2009 at 19:04 UTC
    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 ArduousIterator.new 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.

    —John

      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.

        So, you go through all the trouble of cloning the closures and creating the one-off "butted" type. Even if inlined or the compiler otherwise does know the context, it still can't easily figure out that whole methods can be removed.

        If the function knew it was called in numeric context, it could return Array but compute_length();. If optimized over, the property contains no closures, so is easily cut down to just the desired function call.