5

In a module I'm writing there is just one method which requires an additional module, so I wish to make that module optional by not listing it in the depends part of the META6.json file. The method will return a Failure object if the optional module is not available.
I know I can do something like this:

if (try require Optional::Module) !=== Nil {
  # go on
} else {
  # fail
}

Is there any better way to do it?

Fernando Santagata
  • 1,487
  • 1
  • 8
  • 15
  • I think you should be able to use `if $*REPO.candidates('Optional::Module')` or similar, though I couldn't seem to get it to work on my sister. but honestly, unless you're doing it many many times, I think `(try require Foo) !=== Nil` is about as simple and unfussy as you can get. – user0721090601 Feb 11 '21 at 20:39
  • 2
    `.candidates` currently only works on CURI and CURFS, so using it would be more like `say so $*REPO.repo-chain.map(*.?candidates(...).Slip).grep(*.defined)`. One could use `.resolve` directly on `$*REPO` instead, but its probably not any shorter: `say so $*REPO.resolve(CompUnit::DependencySpecification.new(:short-name("Optional::Module")))` – ugexe Feb 12 '21 at 00:26
  • @ugexe thanks! I wasn't sure the exact syntax, but I figured whatever it is, it's not really much simpler than the `try require Foo`. Perhaps with macros someoe could make an `available Foo` macro that rewrites into the `try require` bit – user0721090601 Feb 12 '21 at 02:15

2 Answers2

5

I want to thank everyone who answered or commented on this question.
I benchmarked my proposed solution and the two ones given in the comments to my question:

my $pre = now;
for ^10000 {
  $*REPO.repo-chain.map(*.?candidates('Available::Module').Slip).grep(*.defined);
  $*REPO.repo-chain.map(*.?candidates('Unavailable::Module').Slip).grep(*.defined);
}
say now - $pre; # 13.223087

$pre = now;
for ^10000 {
  $*REPO.resolve(CompUnit::DependencySpecification.new(:short-name("Available::Module")));
  $*REPO.resolve(CompUnit::DependencySpecification.new(:short-name("Unavailable::Module")));
}
say now - $pre; # 3.105257

$pre = now;
for ^10000 {
  (try require Available::Module) !=== Nil;
  (try require Unavailable::Module) !=== Nil;
}
say now - $pre; # 4.963793

Change the module names to match one that you have available on your system and one that you don't.
The comments show the results in seconds on my computer.

Fernando Santagata
  • 1,487
  • 1
  • 8
  • 15
  • So I noodled about with this and wonder. Did you try something like `so $*REPO.installed.first( { $_.meta ~~ "Module" }).elems` ? Just see if the module is in the list of installed ones? Not sure how fast that would be. – Scimon Proctor Feb 12 '21 at 13:32
  • That's super quick if you cache the `$*REPO.installed` sequence (maybe in a state var?) and super duper slow otherwise. (The try require 10000 times on my machine took ~12 seconds. The first from the installed list took ~0.6) – Scimon Proctor Feb 12 '21 at 13:36
  • …and slow it is: when the benchmark finally ended it totaled 1229 seconds. Thanks anyway, it's good to know! – Fernando Santagata Feb 12 '21 at 13:43
  • 2
    Don't call `$*REPO.installed` because only certain types of repos even have a `.installed` method, and you are generally interested if *any* repo can provide the given module (not just the current/head repo). It would be more appropriate to call e.g. `$*REPO.repo-chain.map(*.?installed)` but that wouldn't work with modules provided by a CURFS (`-I.`, `-Ilib`, etc) – ugexe Feb 12 '21 at 13:56
  • Well there we go. I was literally just poking about at things to see what I could find. – Scimon Proctor Feb 12 '21 at 13:59
  • 2
    `.resolve` `.load` and `.need` are notable because it will iterate over the entire repo chain, whereas all other methods like `.candidates` and `.installed` give results for the specific repo they are called on (hence using `$*REPO.repo-chain.map(*.?installed.Slip)`) – ugexe Feb 12 '21 at 14:08
2

Something like this?

my $loaded = False;
CATCH {
    default { $loaded = True }
}
require Optional::Module;
if ( ! $loaded ) {
  # Fail
}
# Go on

In this case it will try and load the module and catch the exception at runtime.

Scimon Proctor
  • 4,558
  • 23
  • 22
  • Isn't it the same as `try require Optional::Module`? What I wrote in my question should catch the exception at runtime. I was looking for something like, say, a way to inspect the module repository (if that's really a better way), or something similar. – Fernando Santagata Feb 11 '21 at 16:08
  • 1
    It is... I realised this after I wrote it but I figured I'd keep it as an answer. Someone mentioned $*REPO in IRC yesterday. It's very lightly documented but a `.^methods` call on it gives some interesting results. You may want to look at that? – Scimon Proctor Feb 12 '21 at 09:56