10

I'm writing a drawing package with some parts, and I have operators and data types scattered througout. However I don't want the users to add the corresponding modules every time, since it would be quite messy, for instance I'd have a Point class, a Monoid role and a Style class in different paths like this

unit module Package::Data::Monoid;
# $?FILE = lib/Package/Data/Monoid.pm6

role Monoid {...}
unit module Package::Data::Point;
# $?FILE = lib/Package/Data/Point.pm6

class Point {...}
unit module Package::Data::Style;
# $?FILE = lib/Package/Data/Style.pm6

class Style {...}

I would like to have a haskell like prelude in lib/Package/Prelude.pm6 with the effect that I can write such scripts

use Package::Prelude;

# I can use Point right away, Style etc...

instead of doing

use Package::Data::Style;
use Package::Data::Point;
use Package::Data::Monoid;

# I can too use point right away, but for users not knowing the
# inner workings it's too overwhelming

I've tried many things:

  • This version doesn't give me the right effect, I have to type the whole path to point, i.e., Package::Data::Point...
unit module Package::Prelude;
# $?FILE = lib/Package/Prelude.pm6
use Package::Data::Style;
use Package::Data::Point;
use Package::Data::Monoid;
  • This version gives me the Point right away, but I get problems with the operators and so on, also I would just like to add automatically everything from the exported routines in the mentioned example packages.
# $?FILE = lib/Package/Prelude.pm6
use Package::Data::Style;
use Package::Data::Point;
use Package::Data::Monoid;

sub EXPORT {
  hash <Point> => Point
     , <Style> => Style
     , <mappend> => &mappend
     ...
}

Do you people know a better and quick way of getting such a prelude-like file?

margolari
  • 651
  • 3
  • 11

1 Answers1

12

Using EXPORT is in the right direction. The key things to know are:

  • Imports are lexical
  • We can use introspection to obtain and access the symbols in the current lexical scope

So the recipe is:

  • use all the modules inside of EXPORT
  • Then extract all the imported symbols and return them as the result from EXPORT

As an example, I create a module Foo::Point, including an operator and a class:

unit module Foo::Point;

class Point is export {
    has ($.x, $.y);
}

multi infix:<+>(Point $a, Point $b) is export {
    Point.new(x => $a.x + $b.x, y => $a.y + $b.y)
}

And, just to demonstrate it can work with multiple modules, also a Foo::Monad:

unit module Foo::Monad;

class Monad is export {
    method explain() { say "Just think of a burrito..." }
}

The goal is to make this work:

use Foo::Prelude;
say Point.new(x => 2, y => 4) + Point.new(x => 3, y => 5);
Monad.explain;

Which can be achieved by writing a Foo::Prelude that contains:

sub EXPORT() {
    {
        use Foo::Point;
        use Foo::Monad;
        return ::.pairs.grep(*.key ne '$_').Map;
    }
}

There's a few oddities in here to explain:

  1. A sub has implicit declarations of $_, $/, and $!. Exporting these would result in a compile-time symbol clash error when the module is use'd. A block only has an implicit $_. Thus we make our life easier with a nested bare block.
  2. The grep is to make sure we don't export our implicitly declared $_ symbol (thanks to the nested block, it's the only one we have to care about).
  3. :: is a way to reference the current scope (etymology: :: is the package separator). ::.pairs thus obtains Pair objects for each symbol in the current scope.

There's a speculated re-export mechanism that may appear in a future Raku language release that would eliminate the need for this bit of boilerplate.

Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129
Jonathan Worthington
  • 29,104
  • 2
  • 97
  • 136
  • Finally, this is exactly the behaviour I was looking for, thanks so much! – margolari Apr 13 '20 at 00:54
  • I don't think this approach works if the re-exported modules export an operator sub, I get an "Cannot import symbol '%?LANG' from 'Project::Prelude', because it already exists in this lexical scope.". If I remove the exported operator from the prelude it works. – stu002 Aug 15 '20 at 06:01
  • NB If you use `unit ... Foo::Prelude;` in the `Foo::Prelude` package, the `sub EXPORT ...` must appear *before* the `unit` line. See the ***Importing* symbols into a package and then *re-exporting* them** section (and its footnotes) in [this SO answer](https://stackoverflow.com/questions/69017360/separating-operator-definitions-for-a-class-to-other-files-and-using-them/69018308#69018308) for more discussion. – raiph Mar 05 '22 at 21:52