Moritz wrote:

> To spin the tale further, we need to think about what happens if
> somebody writes
>
> multi foo(1|2e0) { ... }
>
> so now we have Int|Num. We could explore the most-derived common
> ancestor (Cool), or look into role space (Real, Numeric come to mind),
> or simply error out.

Or maybe we need to reconsider the whole idea that it's appropriate to
infer type from a smartmatched constraint?

Because, having pondered it at some length, I think we could very
reasonably decide that, when providing a constraint, the coder is not
making any reliable implication regarding the associated parameter type.

Let's look at some examples...

1. Suppose we wanted a subroutine that classifies numbers according to a
   very simple scheme.  We might write:

        multi classify($ where 0   ) { 'zero'     }
        multi classify($ where 1..9) { 'digit'    }
        multi classify($           ) { 'sequence' }

   It would be more convenient if we could just write:

        multi classify( 0    ) { 'zero'     }
        multi classify( 1..9 ) { 'digit'    }
        multi classify( $    ) { 'sequence' }

    But we can't, because the current inference rules would infer:

        multi classify(Int       $ where 0   ) { 'zero'     }
        multi classify(List[Int] $ where 1..9) { 'digit'    }
        multi classify(Any       $ where *   ) { 'sequence' }

    which is not helpful. It goes wrong because, except in the first
    version of C<classify>, the type of the parameter constraint does
    not imply the type of parameter.


2. Or suppose we wanted a subroutine that only ever accepts a given
   argument once. We might write:

        sub unseen ($msg) { state %seen; %seen{$msg}++; }

        multi ping($ where &unseen ) { die 'One ping only!' }
        multi ping($msg            ) { say "PING! ($msg)"   }

   It would be more convenient if we could write:

        multi ping({.&unseen}) { die 'One ping only!' }
        multi ping($msg      ) { say "PING! ($msg)"   }

    But we can't, because the current inference rules would make that:

        multi ping(Block $ where {.&unseen}) { die 'One ping only!' }

    which is not helpful. Because, again, the type of the constraint
    does not imply (nor is it even directly related to) the type of the
    parameter.


3. Or suppose we wanted to detect special boundary conditions
   (a case similar to Moritz's example above). We might write:

        multi check_value($ where 0|1e6) { fail 'edge case!' }
        multi check_value($            ) { return True       }

   It would be more convenient if we could write:

        multi check_value(0|1e6) { fail 'edge case!' }
        multi check_value($    ) { return True       }

   But we can't, because the current inference rules would make that:

        multi check_value(Junction $ where 0|1e6) { fail 'edge case!' }
        multi check_value(         $            ) { return True       }

    which is not helpful. Because, as before, the type of the constraint
    is not correlated with the type of the parameter.


In each case (and in the majority of other cases, I suspect) the type
and the constraint value are performing two entirely distinct tasks,
and are often unrelated. Or, at least, unrelated in the sense that the
constraint value is frequently not type-compatible with the type,
because the constraint is verified by smartmatching, which is most often
an operation between values of two unrelated types: integer matched
against block, string matched against list, number matched against
junction, etc. etc.

In other words specifying a constraint value is a way of applying a
smartmatched acceptance test to a parameter, but the type of the
acceptance test is typically totally unrelated to the type of the
parameter.

Which is why it now seems very odd to me that we are currently inferring
parameter types from constraint values.

The type of the constraint value tells us nothing reliable about the
type of the parameter it constrains. It only tells us that the value of
that parameter must be matchable against the value of that constraint.

And that appears to be true even for the very simplest example:

    sub foo($ where 1) { say 'There can be only one!' }

    foo(1);      # Okay
    foo('1');    # Okay
    foo(True);   # Okay
    foo([42]);   # Okay
                 # et cetera...

I ought to be able to write that as:

    sub foo(1) { say 'There can be only one!' }

but I cannot, because the current rules don't even infer a
coercive type.


TL;DR: In general, the type of a parameter's C<where> constraint value
       has little to do with the type of the parameter,
       because constraints are smartmatched, not type-matched.


My proposal, therefore, is that any parameter specified only as a
constraint value simply does not attempt to infer its parameter type at
all, but just retains its default type of Any.

That is, in all cases:

    sub foo( SOME_VALUE ) {...}

is just a shorthand for:

    sub foo($ where SOME_VALUE ) {...}

I do believe this would be both more predictable, more in line with
hacker intuition, and much more useful as well.

Reply via email to