On 9/3/05, Damian Conway <[EMAIL PROTECTED]> wrote:
> Hmmmm. The arity of a given multi might be 3 or 4 or 5.
> 
> If *only* there were a way to return a single value that was simultaneously
> any of 3 or 4 or 5.
> 
> Oh, wait a minute...

Well, we'd better document that pretty damn well then, and provide
min_arity and max_arity, too.   This is one of those places where
Yuval is spot on about autothreading being evil.  This is also one of
those places where I am spot on about Junctive logic being evil.

It looks like returning a junction is the dwimmiest thing we can do here:

    given &code.arity {
        when 1 { code(1) }
        when 2 { code(1,2) }
    }

So if &code is a multimethod that has variants that take two
parameters or three parameters, great, we call it with (1,2), which
will succeed.  And if it has variants that take one parameter or three
parameters, great, we call it with (1), which will succeed.  And if it
has variants that take one parameter or two parameters, um..., great,
we call it with (1), which will succeed.

In that last case though, this is not equivalent to the above:

    given &code.arity {
        when 2 { code(1,2) }
        when 1 { code(1) }
    }

That may be a little... surprising.  Still, it's fixed to succeed
either way, so that's probably okay, right?

But let's do a little thinking here.  You're asking a code reference
for its arity.  It's pretty likely, that unless you are doing
dwimminess calculations (which aren't that uncommon, especially in
Damian's modules), that you have no idea what the arguments are
either.  An example of a function that uses arity is &map.

    sub map (&code, [EMAIL PROTECTED]) {
        gather {
            my @args = @list.splice(0, &code.arity);
            take &code([EMAIL PROTECTED]);
        }
    }

In the best case (depending on our decision), this fails with "can't
assign a junction to an array".  In the worst case, it autothreads
over splice, returning a junction of lists, makes @args a junction of
lists, and then returns a list of junctions for each arity the
multimethod could have taken on.  I don't think that's correct... at
all.   The correct way to have written that function is:

    sub map (&code, [EMAIL PROTECTED]) {
        gather {
            my @args = @list.splice(0, min(grep { ?$_ } &code.arity.states));
            take &code([EMAIL PROTECTED]);
        }
    }

Not quite so friendly anymore.  In order to use this, we had to access
the states of the junction explicitly, which pretty much killed the
advantage of it being a junction.

Junctions are logical travesty, and it seems to me that they cease to
be useful in all but the situations where the coder knows
*everything*.

But I still like them.

Here's how I'm thinking they should work.  This is a minimalistic
approach: that is, I'm defining them in the safest and most limited
way I can, and adding useful cases, instead of defining them in the
richest and most general way possible and forbidding cases that are
deemed "unsafe".

Throw out all your notions about how Junctions work.  We're building
from scratch.

Things that come on the right side of smart match do a role called
Pattern, which looks like this:

    role Pattern {
        method match(Any --> Bool) {...}
    }

Among the things that do pattern are Numbers, Strings, Arrays, ...
(equivalence); Types, Sets, Hashes (membership); Bools, Closures
(truth); and Junctions (pattern grep).  That is, a Junction is just a
collection of Patterns together with a logical operation.  It, in
turn, is a pattern that can be smart-matched against.  Therefore, we
sidestep the issue of the existence of junctions making every ordered
set have exactly one element[1]. because there is nothing illogical
against testing against a pattern.  You can also safely pass it to
functions, and they can use it in their smart matches, and everything
is dandy.

That's it for the base formulation.  A junction is just a pattern, and
it makes no sense to use it outside of the smart-match operator.

But that means that we have to change our idioms:

    if $x == 1 | 2 | 3   {...}
    # becomes
    if $x ~~ 1 | 2 | 3   {...}    # not so bad
    
    if $x < $a | $b      {...}
    # becomes
    if ($x ~~ { $_ < $a } | { $_ < $b }) {...}  # eeeyuck
    if $x < $a || $x < $b {...}  # back to square one

    # from E06
    if any(@newvals) > any(@oldvals) {
        say "Already seen at least one smaller value";
    }
    # becomes
    if (grep { my $old = $_; grep { $_ > $old } @newvals } @oldvals) {
        say "I don't remember what I was going to say because the
condition took so long to type"
    }

So, that sucks.  But I'm beginning to wonder whether we're really
stuck there.  The two yucky examples above can be rewritten, once we
take advantage of some nice properties of orderings:

    if $x < max($a, $b) {...}
    if min(@newvals) > min(@oldvals) {...}

But that's a solution to the specific comparison problems, not the
general threaded logic problem.

Yeah... so, that's what I think about junctions.  This is definitely a
tentative proposal, so if there are cases where junctions in their
current state make a certain problem a lot easier (and I mean
conceptual changes, not minor syntactic issues), then I want to hear
them so we can try to expand this proposal to include that
functionality.  Also remember about hyper-argument foo(>>1,2,3<<)
which has your autothreading base covered.

Luke

[1] 
    $a <= any($a, $b).
    any($a, $b) <= $b.
    Therefore, $a <= $b.
    Everything is <= everything else (we made no assumptions about $a
and $b), and < is anticommutative, so every object is equal to every
other object, and there is only one object.

Reply via email to