Re: Aliasing methods in CPAN roles
Am Montag, den 19.10.2009, 16:43 -0700 schrieb Jon Lang: Raphael Descamps wrote: I personally don't understand why we don't have a exclude and alias operator in Perl 6 but I have not read all the synopses and don't have an overview. I don't think that it's explicitly spelled out anywhere; but the reason is fairly straightforward: exclude and alias would break the interface. You're of course right! It's clearly explained in Apocalypse 12 (Conflict Resolution): A role without implementation degenerates to an interface. I don't know why but I didn't realised before that not implementing exclude and alias was in fact an important design decision: I have probably read to much traits papers and not enough Apocalyses ;) On one side you lose flexibility to resolve some composition conflicts but the fact that a role also define a contract is of course a big win, particulary for a language like perl 6 supporting optional statical typing. The traits paper only focus on dynamic typing. It also explain why perl 6 as a so strong support for delegation, as it is the proposed way to solve composition conflicts. It's time to read Apacalyse 12 again as I am now able to anderstand it :)
Re: Aliasing methods in CPAN roles
Am Freitag, den 16.10.2009, 10:54 +0400 schrieb Richard Hainsworth: Arising out of Freezing Roles is a related question. Suppose I download a module from CPAN with a role I want to use, but it introduces a method that I want that is in conflict with an existing method (say one taken from another CPAN module). How should the method be aliased to prevent it from causing a conflict at class composition time? I personally don't anderstand why we don't have a exclude and alias operator in Perl 6 but I have not read all the synopses and don't have an overview. In the thread Re: YAPC::EU and Perl 6 Roles in last july I already said the following: ---snipp--- The brilliant idea with traits is that it bring back the control to the class consuming the trait and conflicts have to be solved explicitly. The traits paper propose 3 different operators to solve such conflicts: overriding, excluding or aliasing. I definitively think that perl 6 roles should also have an excluding operator because I think that *every* composition conflicts arrising should be solvable by the class comsuming the role. ---snapp--- As a side note, Johnatan give us a example about how to make an alias with user defined traits, but it doesn't help here because a trait is bound to a definition: http://use.perl.org/~JonathanWorthington/journal/39504 My anderstanding is also that that kind of aliasing as defined with a trait is deep: If you alias a recursive method, the call will be done to the aliased one (or am I wrong?). In the original traits paper the aliasing is not deep: to respect the flattening property, the semantic of the role must not change, so aliasing a recursive method will call the original method. It's a known theoretical weakness of the traits paper and freezing roles try to solve this problem. Finally, the interaction between module and role is also interesting and it's not clear to me how Perl 6 solve it: I send a question this August to the mailinglist but sadly had no reply, see Perl 6 modules and classboxes?: ---snipp--- As Perl 6 will be supporting multiple versions installed of the same module and also support import with lexical scoping, I was asking myself if it was possible to combine some of the interresting properties of classboxes like local rebinding, flattening property and the idea that import takes precedence over inheritance. I am absolutly not sure if it fit to the Perl 6 module concept as a whole, but I will be happy to read your comments and what you think about it. A few pointers: classboxes+traits introduction: http://scg.unibe.ch/archive/papers/Berg05dTraitsClassbox.pdf For an in depth description, you can also read the Ph.D. thesis: http://scg.unibe.ch/archive/phd/bergel-phd.pdf To develop the classbox concept, the autors also introduced a module calculus, which also help to describe the difference existing beetween different modules systems: (such a module calculus can also help to better anderstand the interaction beetween different languages): http://scg.unibe.ch/archive/papers/Berg05cModuleDiversity.pdf ---snapp--- Raphael
Re: Aliasing methods in CPAN roles
On 2009-Oct-18, at 3:44 pm, Jon Lang wrote: David Green wrote: I would expect that role Logging { method log(Numeric $x:) {...} } means the invocant is really of type Numeric Logging, without Logging having to do Numeric. On the other hand, I can see that strictly that might not make sense, so perhaps I really do need to create a compound NumLog type first, so I can have method log(NumLog:)? I think you should need to do this. That's cumbersome, though. I don't want to create some new type, that happens to do Numeric and Logging (in addition to other stuff it might do); I want to affect everything else that does both roles. That is, I don't want my special log() method to work only for other types that explicitly do NumLog; I want it to work for any type that directly does Numeric does Logging. In fact, I can already refer to that combination without needing to create a compound type; for example, in a signature I can say (Numeric Logging $x:). I want my log() method to apply to $x there, even though it's Numeric Logging and not NumLog. I don't like dangling methods outside of any role though, either. What I want to be able to do is say: role Logging { method log(Numeric $x:) {...} } and have it treat the invocant as something that does Logging (naturally, since it's part of the Logging role), and that also does Numeric (as specified in the sig). It's too reasonable a thing to do not to have a reasonable way to express it. Of course, I could do something like this: role Logging { method log { given self { when Numeric {...} when Stringy {...} etc. } } } that is, I can do the dispatching myself. But I shouldn't have to, especially in cases that are more complex than this simple example. (But I'll suggest something new for - in general: what if $x - Numeric with no $n variable were shorthand for $x - Numeric $x is rw, i.e. a shorthand that used the same variable name inside the block as the one being passed in? That would be useful in cases like this where we don't particularly want to rename $x.) It wouldn't always be workable; for instance, @a - Numeric, Stringy { ... } would grab the first two element of @a and would put them into parameters; but there would be no obvious names to assign to those parameters. Yes, and I think it's OK for a shortcut like that to be available only in simple cases. -David
Re: Aliasing methods in CPAN roles
Raphael Descamps wrote: I personally don't understand why we don't have a exclude and alias operator in Perl 6 but I have not read all the synopses and don't have an overview. I don't think that it's explicitly spelled out anywhere; but the reason is fairly straightforward: exclude and alias would break the interface. Take Stringy as an example: when a class says does Stringy, it's making certain promises about its syntax and semantics: e.g., it will have a method say, and method say should result in sending a string of text to an output stream. Thus, any routine that asks for Stringy $x as one of its parameters should be able to put $x.say in its code and get the expected results. But if Foo does Stringy but excludes or aliases .say, a routine that asks for a Stringy $x but receives a Foo $x will run into problems the moment $x.say shows up in its code. If .say was excluded, the semantics are no longer available at all. If it was aliased, the semantics are still available under another name; but that does the routine no good, because it has no idea what the new name is, or even that it exists. Either way, $x.say will not do what the routine intended it to do. The interface is broken. -- Jonathan Dataweaver Lang
Re: Aliasing methods in CPAN roles
Raphael Descamps wrote: In the original traits paper the aliasing is not deep: to respect the flattening property, the semantic of the role must not change, so aliasing a recursive method will call the original method. It's a known theoretical weakness of the traits paper and freezing roles try to solve this problem. It's a problem that doesn't exist if you don't alias. However, you run into another problem; namely, what to do if two roles provide semantically incompatible definitions for the same method. To be fair, ailasing doesn't solve the problem either, for the reasons that I outlined in my last post (i.e., aliasing breaks the interface). And freezing roles doesn't solve the problem either; it just specifies which role is broken in the combined interface. As far as I can tell, there are only two solutions that actually solve the problem: don't compose two roles that have incompatible methods, or find a way for the incompatible definitions to coexist under the same name. The former approach works off of the theory that if the names are the same, the semantics ought to be compatible; and thus incompatible semantics are a sign of poor design of the base roles. In an environment where the programmer has the ability to rewrite everything with which he's dealing, this makes a great deal of sense. But as Richard pointed out, CPAN is a counterexample to this: it is unreasonable to assume that two modules imported from CPAN, written in isolation by different authors, will never provide conflicting roles due to nothing more than conflicting naming conventions - roles that, in concept, ought to be able to be used together. As I understand things, Richard's proposed solution is to alias one of the offending methods during the import, effectively rewriting the source module to use a different name for the offending method, for the sole purpose of exporting to the target application. IMHO, this only works if you follow the chain of compositions all the way and alias everything. That is: role Foo { method x; } role Bar does Foo { method x; } role Baz does Foo { method x; } If you want to alias Bar.x on import, there should be an implicit aliasing of Foo.x as well, which would lead to the implicit aliasing of Baz.x too. It's the only way to avoid broken interfaces: you need to change all related interfaces to remain compatible with the one that you change, both up and down the composition chain. Needless to say, this strikes me as impractical, due to the effort involved in figuring out what needs to be aliased and what doesn't. Another possibility would be to borrow a page from XML Namespaces, which addressed a similar problem: allow the programmer to require imported elements to be referenced in terms of the module from which they were imported. E.g.: use Kennel prefix Foo; # imports role Dog use Forest prefix Bar; # imports role Tree class Dogwood does Foo-Dog does Bar-Tree { ... } my $dogwood is Dogwood; $dogwood.Foo-bark; $dogwood.Bar-bark; The idea here is that prefix Foo and prefix Bar cause every name that gets imported from that module to be prefixed with that string. So class Dogwood wouldn't have a bark method: it would have a Foo-bark method and a Bar-bark method. IOW, the above would be equivalent to: role Foo-Dog { ... method Foo-bark { ... } ... } role Bar-Tree { ... method Bar-bark { ... } ... } class Dogwood does Foo-Dog does Bar-Tree { ... } my $dogwood is Dogwood; $dogwood.Foo-bark; $dogwood.Bar-bark; -- Jonathan Dataweaver Lang
Re: Aliasing methods in CPAN roles
On 2009-Oct-17, at 1:55 am, Jon Lang wrote: This implies that both Logging and Math do Numeric, since the invocant ought to be of a type that the class does. I would expect that role Logging { method log(Numeric $x:) {...} } means the invocant is really of type Numeric Logging, without Logging having to do Numeric. On the other hand, I can see that strictly that might not make sense, so perhaps I really do need to create a compound NumLog type first, so I can have method log(NumLog:)? Or can I create a method outside of any role: role Numeric {...} role Logging {...} method log(Numeric Logging $x:) {...} (of course, that might be implicitly creating an anonymous compound type for me...) I think that what you're actually looking for (for the purpose of illustration) is Logging::log:(Numeric $x:) and Numeric::log: (Numeric $x:). Oh, yes! If $x does Numeric and $x does Logging, then it has a class that has already encountered the potential conflict and resolved it in some way. For example: class Baz does Numeric does Logging { method log(Numeric $x:) {$x.Numeric::log;} method log(Logging $x:) {$x.Logging::log;} } #`Baz postpones the decision until it knows which role it's being asked to play: Numeric or Logging. Baz illustrates my proposal: if $x is a Baz, it will need to check the context to see if it's supposed to be acting like a Numeric or like a Logging, and will act accordingly - or it will complain about ambiguity if it can't figure out which role to play. And the definition for Baz works because Logging does Numeric. I suppose given that I want Logging's method log(Numeric Logging:) rather than its log(Any Logging:), the second method there should really be: method log(Numeric Logging $x:) {$x.Logging::log;} You cannot define a class that does Logging and does Numeric without defining at least one log method, because they conflict; and a class must somehow resolve all such conflicts. OK; although it seems reasonable to have some sugar for the obvious kind of keep them all methods like in this example. In fact, we probably have that already, by declaring a proto log that makes the others all work according to their sigs. And my mistake was thinking that you could have the same sig doing different things, but really the second sig is log(Numeric Logging:). (The only way to have the same sig twice would be something like if Logging defined a special version of log() for Numeric objects, while Numeric defined a special log() for Logging objects -- but semantically that ought to mean the same thing in both cases, so we do want a single method to handle that.) In the Baz case, it addresses the matter by making two options available according to the role being played: Numeric or Logging. All you have to do then is to somehow indicate which role is being played. If you can't tell by the routine's signature, my own preference would be to make it explicit by means of a given block: given Logging $x { .log } # logs like a Logging given Numeric $x { .log } # logs like a Numeric I also thought given sounded good for this, but it would have to work differently from a normal given: if $x doesn't do Logging, then it needs to skip the block. (Also, it looks very close to casting: given Logging($x). Maybe something a bit more visually distinctive would be helpful, something like given $x as Logging, etc.?) But I could see other alternatives: .log given Logging $x; # assumes the inclusion of a given statement modifier. I think given, as either a modifier or a block, is the prettiest syntax. $x - Numeric $n { ... ; $n.log ; ... } What I like about this is using a sig to apply the context, so no new syntax is needed. (But I'll suggest something new for - in general: what if $x - Numeric with no $n variable were shorthand for $x - Numeric $x is rw, i.e. a shorthand that used the same variable name inside the block as the one being passed in? That would be useful in cases like this where we don't particularly want to rename $x.) $x.log:(Logging:); And I like this way because it's the most compact, inline way to indicate it. -David
Re: Aliasing methods in CPAN roles
David Green wrote: Jon Lang wrote: This implies that both Logging and Math do Numeric, since the invocant ought to be of a type that the class does. I would expect that role Logging { method log(Numeric $x:) {...} } means the invocant is really of type Numeric Logging, without Logging having to do Numeric. On the other hand, I can see that strictly that might not make sense, so perhaps I really do need to create a compound NumLog type first, so I can have method log(NumLog:)? I think you should need to do this. Or can I create a method outside of any role: role Numeric {...} role Logging {...} method log(Numeric Logging $x:) {...} (of course, that might be implicitly creating an anonymous compound type for me...) Last I checked, all methods must be members of a class or role. I think that what you're actually looking for (for the purpose of illustration) is Logging::log:(Numeric $x:) and Numeric::log:(Numeric $x:). Oh, yes! If $x does Numeric and $x does Logging, then it has a class that has already encountered the potential conflict and resolved it in some way. For example: class Baz does Numeric does Logging { method log(Numeric $x:) {$x.Numeric::log;} method log(Logging $x:) {$x.Logging::log;} } #`Baz postpones the decision until it knows which role it's being asked to play: Numeric or Logging. Baz illustrates my proposal: if $x is a Baz, it will need to check the context to see if it's supposed to be acting like a Numeric or like a Logging, and will act accordingly - or it will complain about ambiguity if it can't figure out which role to play. And the definition for Baz works because Logging does Numeric. I suppose given that I want Logging's method log(Numeric Logging:) rather than its log(Any Logging:), the second method there should really be: method log(Numeric Logging $x:) {$x.Logging::log;} I suppose that that would work, too. (The only way to have the same sig twice would be something like if Logging defined a special version of log() for Numeric objects, while Numeric defined a special log() for Logging objects -- but semantically that ought to mean the same thing in both cases, so we do want a single method to handle that.) And if you limit yourself to referencing types that the method's role does, this won't be an issue. If you can't tell by the routine's signature, my own preference would be to make it explicit by means of a given block: given Logging $x { .log } # logs like a Logging given Numeric $x { .log } # logs like a Numeric I also thought given sounded good for this, but it would have to work differently from a normal given: if $x doesn't do Logging, then it needs to skip the block. (Also, it looks very close to casting: given Logging($x). Maybe something a bit more visually distinctive would be helpful, something like given $x as Logging, etc.?) IMHO, given $x { ... } is effectively syntactic sugar for $x - $_ { ... }, and given Numeric $x { ... } would be syntactic sugar for $x - Numeric $_ { ... }. If $x doesn't do Numeric, the default behavior should be a fail. $x - Numeric $n { ... ; $n.log ; ... } What I like about this is using a sig to apply the context, so no new syntax is needed. (But I'll suggest something new for - in general: what if $x - Numeric with no $n variable were shorthand for $x - Numeric $x is rw, i.e. a shorthand that used the same variable name inside the block as the one being passed in? That would be useful in cases like this where we don't particularly want to rename $x.) It wouldn't always be workable; for instance, @a - Numeric, Stringy { ... } would grab the first two element of @a and would put them into parameters; but there would be no obvious names to assign to those parameters. -- Jonathan Dataweaver Lang
Re: Aliasing methods in CPAN roles
David Green wrote: Aha, so the bark:(Dog:) syntax identifies the method by its signature as well, thus distinguishing it from the .bark:(Tree:) method. This works fine when the sigs can distinguish the invocants, which is very common. However, I could have ambiguous methods even including the signatures. Suppose I have a Logging role that provides a log() method for printing some info about a variable. In particular, I have method log(Numeric $x:) { ... } because I want to handle Nums specially (say, round them off before printing). Meanwhile, suppose I also have Math::log(Numeric $x:). So you have Logging::log:(Numeric $x:), and you have Math::log:(Numeric $x:). This implies that both Logging and Math do Numeric, since the invocant ought to be of a type that the class does. (And incidentally, this brings up another issue: as written, Math isn't a class; it's a module. Modules generally don't do roles, assuming that they even can.) Note further that in the setting, you actually have Math::log:(Numeric $x). Modules usually don't have methods, and so their routines generally don't have invocants. I think that what you're actually looking for (for the purpose of illustration) is Logging::log:(Numeric $x:) and Numeric::log:(Numeric $x:). Continuing on with that: If $x does Numeric and does Logging, then $x.log won't be able to decide which method to call, unless maybe it's in a sub like foo(Numeric $x) that can know to provide Numeric context to $x. If $x does Numeric and $x does Logging, then it has a class that has already encountered the potential conflict and resolved it in some way. For example: class Foo does Numeric does Logging { method log(Numeric $x:) {$x.Numeric::log;} } # Foo picks out the method from Numeric. class Bar does Numeric does Logging { method log(Numeric $x:) {$x.Logging::log;} } # Bar picks out the method from Logging. class Baz does Numeric does Logging { method log(Numeric $x:) {$x.Numeric::log;} method log(Logging $x:) {$x.Logging::log;} } #`Baz postpones the decision until it knows which role it's being asked to play: Numeric or Logging. If $x is a Foo, then $x.log will always behave like Numeric::log; if $x is a Bar, then $x.log will always behave like Logging::log. Baz illustrates my proposal: if $x is a Baz, it will need to check the context to see if it's supposed to be acting like a Numeric or like a Logging, and will act accordingly - or it will complain about ambiguity if it can't figure out which role to play. And the definition for Baz works because Logging does Numeric. You cannot define a class that does Logging and does Numeric without defining at least one log method, because they conflict; and a class must somehow resolve all such conflicts. Outside foo, or inside a sub like bar(Any $x), I need some other way to indicate which log method I mean. $x.log:(Numeric:) won't work here, because both roles provide a method with that name and signature. As I indicated above, it will work, because $x.WHAT will have addressed the matter already. In the Foo and Bar cases, it addresses the matter by picking one or the other and preventing access to the one it doesn't pick; this is a viable stratagem if Logging and Numeric are semantically similar (and, seeing as how Logging does Numeric, they probably are). In the Baz case, it addresses the matter by making two options available according to the role being played: Numeric or Logging. All you have to do then is to somehow indicate which role is being played. If you can't tell by the routine's signature, my own preference would be to make it explicit by means of a given block: given Logging $x { .log } # logs like a Logging given Numeric $x { .log } # logs like a Numeric But I could see other alternatives: .log given Logging $x; # assumes the inclusion of a given statement modifier. $x - Numeric $n { ... ; $n.log ; ... } $x.log:(Logging:); The point is that you're never going to have two different log:(Numeric:) methods in the same class. -- Jonathan Dataweaver Lang
Re: Aliasing methods in CPAN roles
On 2009-Oct-16, at 12:54 am, Richard Hainsworth wrote: Is there syntactic sugar for aliasing the conflicting method? Eg. something like does XML :db-writexml-db-write; There needs to be something more than sugar: making a new class or role with different methods will break substitutability. However, we could have a feature which aliases a method name in a given scope or context; elsewhere, the original name would still be visible. So you could pass your new XML-plus-SQL object to do-xml-stuff(XML $x) and it would know to treat it as an XML object with the proper .db-write method. (Incidentally, we could use something similar for renaming imported symbols, though in that case it would be only sugar. Currently, we can choose to import something or not, but if a module exports something, it presumably has a good reason; rather than simply not importing the sub/etc., it would be handy to be able to import it under another name. use Bar foo;# import Bar::foo use Baz :fooboo;# import Baz::foo as boo() use Baz foo='boo'; # or spelled this way ) Moreover, suppose that the two modules have roles with the same name, eg., suppose XML-Database-Module and SQL-Database both define a role 'Writable'. How could these one (or both) of these roles be aliased? I suppose the polite thing would be for them to define roles (or anything else) inside their own namespaces: XML-Database- Module::Writable. Meanwhile, on 2009-Oct-14, at 7:58 pm, Jon Lang wrote: Another clarification: there's a subtle but important difference between $dogwood.bark:(Dog:).() and $dogwood.Dog::bark(). The former calls a Dogwood method that has an invocant that does Dog; the latter calls a Dog method. That is: $dogwood.bark:(Dog:).(); # calls Dogwood::bark:(Dog:) $dogwood.Dog::bark();# calls Dog::bark:() Aha, so the bark:(Dog:) syntax identifies the method by its signature as well, thus distinguishing it from the .bark:(Tree:) method. This works fine when the sigs can distinguish the invocants, which is very common. However, I could have ambiguous methods even including the signatures. Suppose I have a Logging role that provides a log() method for printing some info about a variable. In particular, I have method log(Numeric $x:) { ... } because I want to handle Nums specially (say, round them off before printing). Meanwhile, suppose I also have Math::log(Numeric $x:). If $x does Numeric and does Logging, then $x.log won't be able to decide which method to call, unless maybe it's in a sub like foo(Numeric $x) that can know to provide Numeric context to $x. Outside foo, or inside a sub like bar(Any $x), I need some other way to indicate which log method I mean. $x.log:(Numeric:) won't work here, because both roles provide a method with that name and signature. What if all roles' methods got automatic aliases, to a long(er) name, e.g. the .log method could be referred to as such, or as .Logging`log()? That at least would provide a fully-qualified way to refer to methods no matter where they came from originally, and also allow short names to be used where unambiguous. (I guess this parallels what we already have for subs, etc., except methods would be automatically exported into new roles or classes so that we can use short names. I don't know what the actual syntax should be -- I only used ` above for lack of anything better, since the obvious .Logging::log means something else.) -David