margolari
margolari

Reputation: 671

Use Haskell like Prelude modules in a module in raku

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:

unit module Package::Prelude;
# $?FILE = lib/Package/Prelude.pm6
use Package::Data::Style;
use Package::Data::Point;
use Package::Data::Monoid;
# $?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?

Upvotes: 10

Views: 209

Answers (1)

Jonathan Worthington
Jonathan Worthington

Reputation: 29454

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.

Upvotes: 12

Related Questions