—#
# Copyright (c) 2015-2023 Christian Jaeger, copying@christianjaeger.ch
#
# This is free software, offered under either the same terms as perl 5
# or the terms of the Artistic License version 2 or the terms of the
# MIT License (Expat version). See the file COPYING.md that came
# bundled with this file.
#
# Depends: ()
=head1 NAME
FunctionalPerl - functional programming in Perl
=head1 SYNOPSIS
use FunctionalPerl;
FunctionalPerl->VERSION # or $FunctionalPerl::VERSION
# The actual modules are in the FP:: namespace hierarchy, like:
use FP::List;
# But you can also import sets of modules from here, e.g.:
#use FunctionalPerl qw(:sequences :repl);
=head1 DESCRIPTION
Allows Perl programs to be written with fewer side effects.
See the L<Functional Perl|http://functional-perl.org/> home page.
=head1 EXPORTS
L<FunctionalPerl> also acts as a convenience re-exporter, offering
tags to load sets of modules. (It also has one normal export:
`expand_import_tags`, see below.)
Note that the tags and the sets of modules are very much alpha. If you
want to have a better chance of code not breaking, import the modules
you want directly.
Tags can be expanded via:
=for test
use FunctionalPerl qw(expand_import_tags);
my ($modules, $unused_tags, $nontags) = expand_import_tags(qw(:dev :most not_a_tag));
is $$modules{"FP::Failure"}, 2; # number of times used.
is_deeply $unused_tags,
[':all', ':all_sequences', ':ast', ':csv', ':dbi', ':fix', ':git', ':io', ':paths', ':pxml', ':rare', ':trampolines', ':transparentlazy'];
is_deeply $nontags, ['not_a_tag'];
=head1 SEE ALSO
This is the list of supported import tags and the modules and other tags that they import:
C<:all> -> C<:dev>, C<:io>, C<:most>, C<:rare>
C<:all_sequences> -> C<:primary_sequences>, L<FP::SortedPureArray>, L<FP::StrictList>
C<:ast> -> L<FP::AST::Perl>
C<:autobox> -> L<FP::autobox>
C<:chars> -> L<FP::Char>
C<:csv> -> L<FP::Text::CSV>
C<:datastructures> -> C<:chars>, C<:maps>, C<:numbers>, C<:sequences>, C<:sets>, C<:tries>
C<:dbi> -> L<FP::DBI>
C<:debug> -> C<:equal>, C<:show>, L<Chj::Backtrace>, L<Chj::pp>, L<Chj::time_this>
C<:dev> -> C<:debug>, C<:repl>, C<:test>, L<Chj::ruse>
C<:doc> -> L<FP::Docstring>
C<:equal> -> L<FP::Equal>
C<:failures> -> L<FP::Either>, L<FP::Failure>
C<:fix> -> L<FP::fix>
C<:functions> -> C<:equal>, C<:failures>, C<:show>, L<FP::Cmp>, L<FP::Combinators>, L<FP::Combinators2>, L<FP::Currying>, L<FP::Div>, L<FP::Memoizing>, L<FP::Ops>, L<FP::Optional>, L<FP::Predicates>, L<FP::Untainted>, L<FP::Values>
C<:git> -> L<FP::Git::Repository>
C<:io> -> L<Chj::tempdir>, L<Chj::xIO>, L<Chj::xhome>, L<Chj::xopen>, L<Chj::xopendir>, L<Chj::xoutpipe>, L<Chj::xperlfunc>, L<Chj::xpipe>, L<Chj::xtmpfile>, L<FP::IOStream>
C<:lazy> -> C<:streams>, L<FP::Lazy>, L<FP::Weak>
C<:maps> -> L<FP::Hash>, L<FP::PureHash>
C<:most> -> C<:autobox>, C<:datastructures>, C<:debug>, C<:doc>, C<:equal>, C<:failures>, C<:functions>, C<:lazy>, C<:show>
C<:numbers> -> L<FP::BigInt>
C<:paths> -> L<FP::Path>
C<:pxml> -> L<PXML::Serialize>, L<PXML::Util>, L<PXML::XHTML>
C<:rare> -> C<:csv>, C<:dbi>, C<:fix>, C<:git>, C<:paths>, C<:trampolines>
C<:repl> -> L<FP::Repl>, L<FP::Repl::AutoTrap>
C<:sequences> -> C<:streams>, L<FP::Array>, L<FP::Array_sort>, L<FP::List>, L<FP::MutableArray>, L<FP::PureArray>
C<:sets> -> L<FP::HashSet>, L<FP::OrderedCollection>
C<:show> -> L<FP::Show>
C<:streams> -> L<FP::IOStream>, L<FP::Stream>, L<FP::Weak>
C<:test> -> L<Chj::TEST>
C<:trampolines> -> L<FP::Trampoline>
C<:transparentlazy> -> C<:streams>, L<FP::TransparentLazy>, L<FP::Weak>
C<:tries> -> L<FP::Trie>
=head1 NOTE
This is alpha software! Read the status section in the package README
or on the L<website|http://functional-perl.org/>.
=cut
# **NOTE** there is no need to keep SEE ALSO in sync with the definitions,
# **NOTE** running meta/update-pod (at release time) will take care of it.
package
FunctionalPerl;
use
strict;
use
warnings;
our
@EXPORT
= ();
our
@EXPORT_OK
=
qw(expand_import_tags)
;
our
%EXPORT_TAGS
= ();
our
$VERSION
=
"0.72.76"
;
# Export tag to modules and/or other tags; each module will be
# imported with ":all" by default. Where a module name contains " = ",
# the part after the " = " is the comma-separated list of tag names to
# import.
# NOTE: the documentation in "SEE ALSO" is auto-generated from this,
# you do not need to keep it in sync manually.
our
$export_desc
= +{
":autobox"
=> [
qw(FP::autobox=)
],
":streams"
=> [
qw(FP::Stream FP::IOStream FP::Weak)
],
":lazy"
=> [
qw(FP::Lazy :streams FP::Weak)
],
":transparentlazy"
=> [
qw(FP::TransparentLazy :streams FP::Weak)
],
":failures"
=> [
qw(FP::Failure FP::Either)
],
":doc"
=> [
qw(FP::Docstring)
],
":show"
=> [
qw(FP::Show)
],
":equal"
=> [
qw(FP::Equal)
],
":debug"
=> [
qw(:show :equal Chj::Backtrace Chj::time_this Chj::pp)
],
":test"
=> [
qw(Chj::TEST)
],
":repl"
=> [
qw(FP::Repl FP::Repl::AutoTrap)
],
":dev"
=> [
qw(:repl :test :debug Chj::ruse)
],
":functions"
=> [
qw(FP::Combinators FP::Combinators2
FP::Cmp
FP::Ops FP::Div
FP::Predicates
FP::Optional FP::Values
FP::Memoizing FP::Currying
FP::Untainted
:show :equal :failures)
],
":git"
=> [
qw(FP::Git::Repository)
],
":pxml"
=> [
qw(PXML::Util PXML::XHTML PXML::Serialize)
],
":ast"
=> [
qw(FP::AST::Perl)
],
":numbers"
=> [
qw(FP::BigInt)
],
":chars"
=> [
qw(FP::Char)
],
":sequences"
=> [
qw(FP::List FP::MutableArray
FP::Array FP::Array_sort
FP::PureArray
:streams)
],
":all_sequences"
=> [
qw(:primary_sequences
FP::StrictList
FP::SortedPureArray)
],
":maps"
=> [
qw(FP::Hash FP::PureHash)
],
":sets"
=> [
qw(FP::HashSet FP::OrderedCollection)
],
":tries"
=> [
qw(FP::Trie)
],
":datastructures"
=> [
qw(:chars :numbers :sequences :maps :sets :tries)
],
":io"
=> [
qw(Chj::xIO Chj::xopen Chj::xtmpfile= Chj::tempdir
Chj::xpipe= Chj::xoutpipe= Chj::xopendir= Chj::xperlfunc
Chj::xhome
FP::IOStream)
],
":dbi"
=> [
qw(FP::DBI=)
],
":csv"
=> [
qw(FP::Text::CSV)
],
":fix"
=> [
qw(FP::fix)
],
":trampolines"
=> [
qw(FP::Trampoline)
],
":paths"
=> [
qw(FP::Path)
],
":most"
=> [
qw(:lazy :datastructures :equal :show :functions :failures :debug
:autobox :doc)
],
":rare"
=> [
qw(:csv :paths :git :dbi :trampolines :fix)
],
":all"
=> [
qw(:most :rare :io :dev)
],
};
sub
check_off {
@_
== 3 or
die
"bug"
;
my
(
$tag
,
$seen_tags
,
$seen_modules
) =
@_
;
my
$vals
=
$$export_desc
{
$tag
} or
do
{
Carp::croak(
"unknown tag '$tag'"
);
};
for
my
$tag_or_module
(
@$vals
) {
if
(
$tag_or_module
=~ /^:/) {
$$seen_tags
{
$tag_or_module
}++;
check_off(
$tag_or_module
,
$seen_tags
,
$seen_modules
);
}
else
{
$$seen_modules
{
$tag_or_module
}++;
}
}
}
sub
expand_import_tags {
# Arguments: tag names and other things. Returns (which tag names
# are unused, used modules, the other things).
my
@tags
=
grep
{/^:/}
@_
;
my
$seen_tags
= +{
map
{
$_
=> 1 }
@tags
};
my
$seen_modules
= +{};
for
my
$tag
(
@tags
) {
check_off
$tag
,
$seen_tags
,
$seen_modules
;
}
(
$seen_modules
,
[
sort
keys
%{ FP::HashSet::hashset_difference(
$export_desc
,
$seen_tags
) }
],
[
grep
{ not /^:/ }
@_
]
)
}
sub
split_moduledesc {
my
(
$module_and_perhaps_tags
) =
@_
;
my
(
$module
,
$maybe_tags
)
=
$module_and_perhaps_tags
=~ m{^([^=]+)(?:=(.*))?}
or
die
"no match"
;
(
$module
,
$maybe_tags
)
}
sub
export_desc2pod {
join
(
""
,
map
{
my
$a
=
$$export_desc
{
$_
};
"C<$_> -> "
.
join
(
", "
,
map
{
if
(/^:/) {
"C<$_>"
}
else
{
my
(
$module
,
$maybe_tags
) = split_moduledesc
$_
;
"L<$module>"
}
}
sort
@$a
)
.
"\n\n"
} (
sort
keys
%$export_desc
)
)
}
sub
import
{
my
$pack
=
shift
;
my
$caller
=
caller
;
my
(
$modules
,
$_unused_tags
,
$nontags
) = expand_import_tags(
@_
);
$pack
->export_to_level(1,
$caller
,
@$nontags
);
for
my
$module_and_perhaps_tags
(
sort
keys
%$modules
) {
my
(
$module
,
$maybe_tags
) = split_moduledesc
$module_and_perhaps_tags
;
my
@tags
=
split
/,/,
$maybe_tags
//
":all"
;
my
$path
=
$module
;
$path
=~ s/::/\//sg;
$path
.=
".pm"
;
# Do not die right away in an attempt at making this more
# usable for users where some of the modules don't work:
if
(
eval
{
require
$path
;
1
}
)
{
$module
->
import
::into(
$caller
,
@tags
)
}
else
{
my
$e
= $@;
my
$estr
=
"$e"
;
$estr
=~ s/\n.*//s
unless
$ENV
{FUNCTIONALPERL_VERBOSE};
warn
"NOTE: can't load $module: $estr"
;
}
}
}
1