The use case would be where the arguments are expected to be filled in by the function - ie, as additional return types.
Consider if we have `A <= B <= C`, and a function:
int Copy([out] Array<B> dest, [in] Array<B> src);
Given some potential inputs:
Buffer<A> asrc = ...
Buffer<B> bsrc = ...
Buffer<C> csrc = ...
Buffer<A> adest = allocate(...)
Buffer<B> bdest = allocate(...)
Buffer<C> cdest = allocate(...)
The following should be true:
Copy(adest, asrc); // No! - How would Copy know how to copy `A` values when it only knows about `B`?
Copy(adest, bsrc); // No! - src argument is OK, but how can it downcast them to `A`?
Copy(adest, csrc); // No! - Same as above, and src elements must be at least `B`s`.
Copy(bdest, asrc); // Ok. - Any `A` in the src are interpreted as `B`s.
Copy(bdest, bsrc); // Ok, - all values are interpreted as `B`s.
Copy(bdest, csrc); // No! - Argument elements must be at least `B`s`.
Copy(cdest, asrc); // Ok - values are interpreted as `B`s in src, and as `C`s in dest.
Copy(cdest, bsrc); // Ok
Copy(cdest, csrc); // No! Argument elements must be at least `B`s`.
If the `dest` argument were contravariant, it would permit invalid copies and forbid valid ones.
Copy(adest, asrc); // pass (wrong)
Copy(adest, bsrc); // pass (wrong)
Copy(adest, csrc); // fail
Copy(bdest, asrc); // pass
Copy(bdest, bsrc); // pass
Copy(bdest, csrc); // fail
Copy(cdest, asrc); // fail (wrong)
Copy(cdest, bsrc); // fail (wrong)
Copy(cdest, csrc); // fail
In a purely functional setting, you should not need a covariant parameter type because all writes would end up in the return type.
copy : Array<B> -> Array<B>