Closed vrurg closed 5 years ago
To extend the subject a little bit, I'd like to confirm what was expected: two user modules of two different versions provide two different classes too. What it could end up with? Say, a class Foo
provided by two versions of Foo
modules, namely v0.1.0
and v0.2.0
.
Then, lets assume there're 3rd party classes Bar1
and Bar2
we use. The former one is older and inherits from Foo:ver<0.1.0>
whereas the latter is newer and inherits from Foo:ver<0.2.0>
. Then here is what we get:
say Bar1.^mro;
say Bar2.^mro;
say Bar1.^mro[1] ~~ Bar2.^mro[1];
say Bar1.^mro[1].^ver;
say Bar2.^mro[1].^ver;
outputs:
((Bar1) (Foo) (Any) (Mu))
((Bar2) (Foo) (Any) (Mu))
False
v0.1
v0.2.0
Because the end-user may not and must not know the internals of the modules he is using this situation would be absolute LTA to him.
Just some tentative thoughts.
The core of the problem is types not matching to each other. I think there must be a way to proclaim a type object to be a different version of an existing one. Few options I have in my mind:
CORE
classes could be identified purely by their names. A ClassHOW
(or rather Metamodel::Versioning
) could get a new attribute language-revision
which would allow to distinguish two classes for an end-user.
NOTE There is a minor technical problem with the fact that many of CORE
classes are stubbed in BOOTSTRAP
and cannot be just declared in a CORE.x.setting
.
A trait. The worst solution, but I'd like to consider it anyway. So, a declaration like:
class Foo is version-of(Foo) { ... }
could create a link between versions.
On IRC ugexe suggested to use auth
. This could be a solution for user-land modules (one author && one name == one class). But useless for CORE
unless we suggest an implicit auth
. But this would basically return us to the first option.
Second problem is coercion methods on Any
as well as any other use of a type object. But it would require separate resolution in each use case.
Turns out there is no way to have another implementation of the same class in another CORE.setting.
Well, if it's a different implementation, is it in any sense the same class?
Let's take the specific example of QuantHash
, and the proposal that it becomes Iterable
in 6.e. The Iterable
role is used to control flattening behavior. If we were to type-constrain a variable to the Set
of 6.c, and depend on it functioning as an item in flattening context, then it would be quite appropriate that a 6.e
Set
would be considered incompatible - because it could break the behavior of the code. That's just type constraints doing their job.
(As an aside: I'm not sure whether it even should become Iterable
- my recollection is that Set
is not by design, and that was intentional in order to ease sets of sets; alas, searching the IRC log doesn't seem to work so I can't easily dig up the years-ago discussion, but @TimToady may recall more on that).
So far as modules go, I think the common case is that we can evolve roles and classes without breaking their compatibility. A :ver
on the type would thus be a mechanism to indicate incompatibility - at which point, once again, we can argue that types are doing their job in rejecting an incompatible value being used that will behave against the expectations of the code consuming it.
The whole thing stems from rakudo/rakudo#1203. And to my view when
my %s1 is Set = <a b c>; my %s2 is Set = %s1; say %s2
results in:
set(set(a b c))
It goes totally against DWIM and common logic. If a set of sets is needed then braces would serve well for this, for example.
Especially contr-intuitive are Bag
s and Mix
es which are even closer to Hash
semantics.
I played with iterable QuantHash
a bit and things looked to be way more natural than it's current implementation. To conclude, I would say that the experiment of making it non-iterable failed.
Now, back to the roots. I actually agree that a new class version is a new class by itself. The more I think of it, the less reasonable I see the idea of proclaiming different versions as the same entity.
But lets assume that it is decided that QuantHash
must be an iterable after all and we get new versions of Set
and its family. It's ok for pre-6.e
code to ignore this fact and be able to deal with the older versions only. But 6.e
code does know about the older classes. How would they handle this situation? Say, a method doesn't care about the new semantics and same code would work well for both versions. Should it be something like:
use v6.e;
...
subset VSet of Any where Set:ver<6.c> | Set:ver<6.e>;
method append (VSet $elems) {
$!collection ∪= $elems;
}
method contains(VSet $elems) {
$elems.Set ⊂ $!collection
}
The second method would coerce any Set
version into Set:ver<6.e>
. VSet
could be replaced with Set:ver<*>
, for example.
Another problem remains: coercion methods on other classes. Again, as a typical example: what Set
must use Any.Set()
? The current implementation will always use 6.c
version because compile-time resolution is used. For correct behavior run-time resolution must be used. One way would be:
method Set {
CLIENT::CORE::<Set>.new(self);
}
Or with special quoting: Q::«Set»
, or with a new pseudo-package CC::<Set>
which is just an alias for CLIENT::CORE::
.
Perhaps these two changes could work to solve the problem.
As far as I can remember, when I asked TimToady what the behaviour of that situation should be, he decided for the current behaviour. AKA, QuantHashes are an item.
I can see the inconsistency. I have no problem in fixing that inconsistency.
@lizmat hope I get you right: the current behavior remains unchanged? If so, I would close all related tickets, including rakudo/rakudo#1203 and #47 .
No, that's not what I'm saying. I'm open to change, but I will not take that decision.
Ok, noted. ;)
Whichever way it goes, the problem of introducing new implementations of classes would stick around anyway. So, I'd like to know if it makes sense in trying to implement typeobject versioning via :ver<>
. Since ClassHOW
and ParametricRoleHOW
do Versioning
but don't set version for core objects, it is possible to set it based on what core is being compiled. This would allow user-land code to correct its behaviour if necessary by checking for, say
Set.^ver eq '6.e'
or by multidispatching:
multi method foo(Set:ver<6.d>) { ... }
multi method foo(Set:ver<6.e>) { ... }
I know that 1) It’s most probably very very difficult to implement and has nothing quite like it yet 2) it doesn’t help much the Set situation 3) it doesn’t help resolve several classes with different :auth<> 4) has classes thinking they’re the same 5) ...probably some other things. But I’d still like to mention Ruby’s Refinements. Their design might be of interest, if only to say « no, we do not want that »
Here’s an example, pseudo code with Ruby syntax.
module Perl6e; # should this be a simple package?
refine Any {
method Set { ... }
}
refine Set {
method append { ... }
}
This is Ruby’s way of making monkey patching lexical (instead of what’s usually done in Ruby...), but it looks a tiny bit similar to our issue at hand
Closing via rakudo/rakudo#3112
Turns out there is no way to have another implementation of the same class in another
CORE.setting
. It could have the same name, would successfully perform under it's corresponding language pragma. But... Those withHEAD
installed can try the following:type-match.pm6:
type-match.p6:
This code outputs
True
withuse v6.c
in type-match.p6 butFalse
withuse v6.e.PREVIEW
! And it makes sense because another implementation ofPseudoStash
is definedCORE.e.setting
.What implications does it has? While trying to solve #47 I realized that just re-declaring
QuantHash
and addingIterable
role to it is not enough, roles and classes consuming it must also migrate intoCORE.e.setting
. Next, I stumble upon a method where an arbitrary object must be coerced intoSet
. TheSet
method for this is defined inAny
. It is creating a newSet
object. ButAny
resides inCORE.setting
and the method will create object using6.c
version ofSet
!Do I need to move
Any
too? But that would mean moving just plain everything (as everything depends onAny
)! And this is where problems are starting. What are we gonna do about all routines with signatures referring toAny
? Because we now have two of those. Wether the signature would match or not will depend on which language version the object has been constructed under!For now I have only the description of the problem, not a single idea of how to deal with it.