New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
{impersonate,chaperone}-procedure should allow reducing procedure arity #2310
Comments
The alternative solution, making them not pretend-to-preserve
This is already possible if you just decide not to use impersonators, and instead return a new function value from the contract-projection. An example of this is shown in both the reference for Building New Contract Combinators and the guide with Building New Contracts, with In that example, if you define The Guide specifically says that "this wrapper function does not cooperate with What the Guide says afterwards suggests a possible middle-ground that I haven't read/heard about yet:
One thing different about this "usable-as" thing is that it isn't symmetric. For example, A less-strict version of impersonators or chaperones could make sure everything preserved this "usable-as" relation, so that "it lets the runtime system know that there is a relationship" between them. I don't know exactly how this would work though, but I think it should be possible because both extremes on either side already exist. |
By “possible”, I really meant “practical”. It’d be easy to implement, but I think it’d be too backwards-incompatible. |
The problem
Currently,
impersonate-procedure
andchaperone-procedure
complain if the wrapper procedure has a smaller arity than the procedure being impersonated. On its surface, this sounds like a reasonable decision: it means that two procedures that areequal?
will both return the same value forprocedure-arity
. However, in practice, this doesn’t make any sense, because one of the primary purposes of procedure impersonators and chaperones is to (morally) restrict procedure arity—function contracts regularly implement arity restriction, though they must do it in an ad-hoc way.For example, consider applying an
->
contract to a function with optional arguments:This
negate
function is obviously restricted to arity 1, and indeed, applying it to any other number of arguments will produce an exception. However, despite this,procedure-arity
reports the wrong thing:> (procedure-arity negate) (arity-at-least 1)
This causes problems when interoperating with other parts of Racket that depend on the value of
procedure-arity
to produce something meaningful. For example,curry
will not behave appropriately onnegate
:This might seem like a small problem, but there are larger, more subtle issues. For example, the contract system’s error reporting itself is worsened, since it cannot rely on first-order checks, and indeed, it can lead to errors that obscure the problem:
In contrast, if
negate
’s arity were restricted, the error would be both immediate and more useful:This can even lead to issues with the impersonation system itself:
The above program seems legal, since
wrap-unary-proc
restricts its argument to unary procedures, but sinceimpersonate-procedure
usesprocedure-arity
to determine if a wrapper procedure is valid, the above program fails! This can even lead to especially egregious situations in which a party in violation of the contract is not blamed even though they ought to be:The above call to
wrap-unary-proc
is illegal, and the caller should be blamed, but they are not due to the inability of the contract system to report the proper arity.One solution
The solution is straightforward: let the contract system stop lying about procedure arity. The most obvious implementation of this is to allow
impersonate-procedure
andchaperone-procedure
to provide wrapper procedures of any arity, and the resulting procedure will have the intersection of their arities. An alternative solution would be to allowprocedure-reduce-arity
to maintainequal?
, as currently it does not:Whichever solution is used, there are many advantages to allowing this, namely that all of the problems described in the previous section disappear. Here’s a short recap:
Contract violations can be signaled more eagerly, since first-order checks can catch arity mismatches in higher-order applications. This can also help improve certain runtime errors produced by Typed Racket, such as the one in Downcasting from Procedure produces an unhelpful error message typed-racket#763.
Functions that care about procedure arity, like
curry
, will do the appropriate thing on functions restricted by contracts.More generally,
procedure-arity
will become more trustworthy, since currently it isn’t reliable—usages of the contract system easily lead to situations whereprocedure-arity
lies.My guess is that some will raise the counterargument that the loss of
(implies (equal? a b) (equal? (procedure-arity a) (procedure-arity b)))
for all proceduresa
andb
is a bad thing, but I believe my last bullet above explains why the current situation is actually no better. Any program that depends on a meaningful relationship betweenequal?
andprocedure-arity
is already wrong, sinceprocedure-arity
is untrustworthy.An alternative solution
Perhaps the previous argument is not compelling to you, and you believe that it is truly unreasonable for two procedures with different arities, as reported by procedure arity, should be
equal?
. I would not personally disagree… returning to thenegate
example from the beginning of this issue, while it is true that equality on functions is ill-defined, I think(equal? - negate)
returning#t
makes little sense. Therefore, one alternate solution to this problem would be to make arity-changing contracts not preserveequal?
.This seems like a more dramatic change. Personally, even if I think that behavior is more reasonable, it seems that ship has sailed—I doubt it’s possible to implement it in Racket today. Still, I think it’s worth mentioning.
The text was updated successfully, but these errors were encountered: