Thinking about MooseX-Types import sugar

As many of you will know by now, Moose comes with its own type system that you can use, extend and introspect. Since the types are handled in a global registry, you have to be concerned about namespaces like with every other perl package.

I started writing MooseX-Types with the objective of writing a tool that provides a solution to the namespace problem by simply enclosing type definitions in a library and turn the types into exports.

Since then, the community has put a lot of thought and effort into the idea and made it into much more. There are reusable type libraries on CPAN for DateTime, Path-Class, data structures, input/output, URIs, and the common extensions of the core types. You can also for some time now refer to imported types in MooseX-Method-Signatures.

With the recent rise of Devel-Declare, we have a lot more options for defining our interfaces. There is already thought and work put into developing nicer sugar for declaring type libraries, so I focused on sugar for importing the types that models nicely onto MooseX-Declare. This article is a summary if what I’ve come up with so far.

Currently, you import types in a MooseX-Declare class declaration in the same way you import them in every other package:

class My::Foo {
    use My::Types::Core      Str => { -as => 'MyStr' };
    use MooseX::Types::Moose qw( Str ArrayRef );

    has foo => (is => 'rw', isa => MyStr);

    method bar (Str $bar, MyStr $baz, ArrayRef $quuxes) { }
}

This is already rather nice and perlish. But it could be more concise. Keep in mind, I think there is nothing more fatal than combining functionality with sugar. This new interface will not replace the current interface, but rather extend and utilise it. If the functionality stays independent of your sugar layer, nobody is forced to use your sugar, and your functionality is extendable and reusable in every context.

There are some things I perceive as solvable issues with the above syntax. I use type libraries quite often these days, and have to type, modify and read these import statements all the time. This is what slowed me down:

  • All the qw( TypeA TypeB ) quoting shouldn’t be necessary for the most common cases.

  • I wouldn’t tend to invent useless name prefixes for types that should be named like existing types but are in another library if I wouldn’t have to type Foo => { -as => 'Bar' } all the time.

  • No matter if you write the use $lib qw( $typea $typeb ); line by hand every time or copy and paste from what’s already there, it is a lot to write just for importing more types from another library, which is pretty common these days.

  • The core types are in MooseX::Types::Moose, the types on CPAN are in uniquely named libraries, and I never name my own libraries in a way that could clash with ones on CPAN. So why do I write the namespace of the library (MooseX::Types::, My::Types::) all the time?

I fought long with these things that bugged me. To demonstrate what I’ve come up with, here’s the above example rewritten in my proposed design:

class My::Foo {
    types from Core:  Str as MyStr,
          from Moose: Str, ArrayRef;

    has foo => (is => 'rw', isa => MyStr);

    method bar (Str $bar, MyStr $baz, ArrayRef $quuxes) { }
}

This has a lot less noise than the former version. If I want to add the Path::Class types into my class definitino, I’d add

from Path::Class: Dir, File

and that’s it. I also think it is quite readable and says what it means. The namespacing is a trickier implementation. MooseX-Declare will probably have some sense of a project namespace in the future, to allow you shortcutting your names. The current idea is that it might be something like this:

namespace My;

The type imports could make use of this too, since they are looking in multiple (extendable) places where a library might be. With the above class declaration and the My namespace, the search order would probably look like this ($LIB being the specified library):

  1. My::Types::$LIB
  2. MooseX::Types::$LIB
  3. $LIB

I think some examples are in order. Here are some type import use cases:

from GlobalIDs: CustomerID, ProductID,
# finds My::Types::GlobalIDs

from Path::Class: Dir, File,
# tries My::Types::Path::Class
# finds MooseX::Types::Path::Class

from OtherProject::Types::Core: SomeStr, SomeHashRef,
# tries My::Types::OtherProject::Types::Core
# tries MooseX::Types::OtherProject::Types::Core
# finds OtherProject::Types::Core

Of course this enforces a certain opinion on type library organization on the user, but it would be extendable (a declarative Catalyst could add the application namespace to the search path), and the functionality is separated from the nice syntax.

I’d very much appreciate any feedback! This isn’t implemented yet, since it took me a while to come up with it, and I’d rather have a few people look at and sanity-probe the idea itself before I have a go at it.

No TrackBacks

TrackBack URL: http://www.catalyzed.org/mt/mt-tb.fcgi/37

Leave a comment

All comments are moderated. Spammers don't waste your time

Sponsored By


Ionzero: Rescue your dev project.

Following

Not following anyone

Note to spammers: all comments are moderated. Don't waste your time