On porting a Moose module
I have been meaning to write this post for a while, but between $work and getting sick I have not really had the brain capacity to do it. Being that I am still a little sick and $work is still busy, I am making no promises about the quality of this post.
In a previous blog post I mentioned that I was going to port my module Bread::Board to p5-mop which I managed to finish up about three weeks ago and then promised to write a post about my experience. Well better late then never, here is that post.
NOTE: if all the Bread::Board terminology gets tedious, skip down to the TL;DR at the end to read my conclusions.
The first thing I will mention is that I did trim some features. Specifically the type-mapping feature, this feature depends on the Moose type systems, which the p5-mop has no equivalent to. I also removed parameterized containers, not really for any good reason, I just wanted to keep focus on the core of Bread::Board. If you don't know what either of these things are, don't worry, I won't mention them again and you can forget about them right ... NOW!
Some of the classes in Bread::Board were very simple to port. Take for instance the Dependency class, which before p5-mop used several Moose features such as default values, predicates and delegation. In the p5-mop version many of these Moose specific features became simple hand-written methods instead of being auto-generated. And in fact, this pattern repeated itself through many of the other classes in Bread::Board. At first I was concerned, but given how simple many of these methods are I found myself not having much of a problem with it. Of course this won't work for all things, things like regexp based delegation cannot easily be done by hand, but it should be easy enough to write a trait to do this for you.
I ran into a little more trouble when I encountered some roles that made use of method modifiers, a good example of this is the WithDependencies role. It wraps both a builder method and a regular method from a role that it consumes, which presented me with an issue since p5-mop does not support method modifiers (and most likely won't in the core). The wrapped builder method is still an issue and most likely would require further redesigning, in this case I punted and will have to really think about this more. The wrapped method was simpler to fix, which I did by simply moving the responsibility out of the role and into the class that consumes the role. While this might not seem as nice it ultimately is much clearer to the reader what is happening. If you look in the Moose version you will see where some subtle role composition differences between the ConstructorInjection class and the BlockInjection class which create a very delicate action-at-a-distance issue that is really had to debug (trust me, I did it). In the p5-mop version this is explicit because we are doing it in the classes directly, not counting on role composition order to do it for us.
The WithDependencies role also made use of trigger to assign a parent link to each dependency. The solution to this was similar in that I had to move (some of) the responsibility for this into the consuming class and the rest into the ported role itself. I am less satisfied with this, but I suspect that some additional refactoring could make it less unappealing to the eye and therefore more tolerable.
The final thing I want to show is the Lifecycle feature, which in the Moose version was accomplished by runtime application of a role to the instance, which was always something I felt dirty about. The p5-mop solution, again, was a little more verbose, but ultimately much cleaner. First I created the Singleton role that gave me the core features the Moose role was giving me, then I created a sublass for each injection type in which I manually composed the role. Again, a little verbose, but much more clear.
While the p5-mop version had more hand-written code, but ultimately felt clearer then the Moose version.
Moose has a lot of features, many of these features take the form of attribute options which generate behaviors related to the attribute's data. The reason that Moose chose to auto generate these things was to prevent the need for users to actually look at the underlying instance Moose had created. Bread::Board heavily uses many of these features, perhaps more then most codebases, so porting it meant manually writing a lot of these methods myself. I didn't really mind though because the p5-mop does a good job of providing access to the underlying attribute data while still protecting the encapsulation of the instance.
I think it is safe to say that the p5-mop forces you as a programmer to be more intentional with your code, in that you must write more code and leaves less to auto-magical code-generation, which in the end feels like a good thing.