# # Copyright (c) 2015-2021 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. # =head1 NAME FP::PureArray =head1 SYNOPSIS use FP::PureArray; use FP::Div 'inc'; my $a = purearray(1,4,5)->map(\&inc); is $a->sum, 13; # can't mutate it: like( (eval { $a->[0]++; 1 } || $@), qr/^Modification of a read-only value attempted/); like( (eval { push @$a, 123; 1 } || $@), qr/^Modification of a read-only value attempted/); is $a->sum, 13; my $b = $a->unsafe_mutable; # efficient but dangerous! $$b[0]++; is $a->sum, 14; # said it was dangerous! is ref($a), 'FP::_::MutableArray'; # see FP::MutableArray is( FP::_::PureArray->null == FP::_::PureArray->null, 1); =head1 DESCRIPTION Perl arrays blessed into the `FP::PureArray` package, inheriting from `FP::Abstract::Pure`, and coming with the functions from `FP::Array` as methods. If you hand someone an FP::PureArray you guarantee that you won't mutate it. This might be enforced in the future by making them immutable (todo). =head1 PURITY `PureArray`s are created to be immutable by default, which enforces the functional purity of the API. This can be disabled by setting `$FP::PureArray::immutable` to false when creating them. Only ever use this during development, if at all. If you need to have efficient updates, use another data structure (L<FP::List> suits many cases, or the to-be written L<FP::Vec>, although at that point updates directly on PureArray may be implemented efficiently, too). Or if you're sure making a PureArray mutable again is safe, you can call the `unsafe_mutable` method. Should lexical analysis get implemented in Perl at some point, a method `mutable` could be offered that safely (by way of checking via the reference count that there are no other users) turns a PureArray back into a mutable array. PureArray implements `FP::Abstract::Pure` (`is_pure` from `FP::Predicates` returns true even if instances were made mutable via setting `$FP::PureArray::immutable`). Values returned from `unsafe_mutable` are in a different class which does *not* implement `FP::Abstract::Pure`. =head1 TODO Create alternative implementation that is efficient for updates on big arrays (perhaps to be called FP::Vec, but to be interoperable). =head1 SEE ALSO Implements: L<FP::Abstract::Pure>, L<FP::Abstract::Sequence>, L<FP::Abstract::Equal>, L<FP::Abstract::Show> L<FP::MutableArray> =head1 NOTE This is alpha software! Read the status section in the package README or on the L<website|http://functional-perl.org/>. =cut package FP::PureArray; use strict; use warnings; use warnings FATAL => 'uninitialized'; use Exporter "import"; our @EXPORT = qw(is_purearray purearray); our @EXPORT_OK = qw(array_clone_to_purearray array_to_purearray); our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]); use FP::Interfaces; use Carp; use Scalar::Util qw(blessed); use FP::Carp; our $immutable = 1; # whether new instances are to be immutable sub is_purearray { @_ == 1 or fp_croak_arity 1; my ($v) = @_; my $r = blessed($v) // return; $v->isa("FP::_::PureArray") } sub purearray { FP::_::PureArray->new_from_array([@_]) } sub array_clone_to_purearray { @_ == 1 or fp_croak_arity 1; FP::_::PureArray->new_from_array([@{ $_[0] }]) } sub array_to_purearray { @_ == 1 or fp_croak_arity 1; FP::_::PureArray->new_from_array($_[0]) } package FP::PureArray::autobox { our $AUTOLOAD; sub AUTOLOAD { my $methodname = $AUTOLOAD; $methodname =~ s/.*:://; my $v = FP::_::PureArray->new_from_array($_[0]); if (my $m = $v->can($methodname)) { goto $m } else { die "no method '$methodname' found for object: $v"; } } } package FP::_::PureArray { use base qw(FP::Array::Mixin); use FP::Interfaces; use Carp; use FP::Carp; use Chj::NamespaceCleanAbove; sub new_from_array { @_ == 2 or fp_croak_arity 2; my ($class, $a) = @_; bless $a, $class; if ($FP::PureArray::immutable) { Internals::SvREADONLY $_, 1 for @$a; Internals::SvREADONLY @$a, 1; } $a } sub purearray { @_ == 1 or fp_croak_arity 1; my $s = shift; $s } sub as_sorted_by { @_ == 2 or fp_croak_arity 2; my ($s, $cmp) = @_; require FP::SortedPureArray; FP::_::SortedPureArray->new_from_purearray($s, $cmp) } sub mutablearray { @_ == 1 or fp_croak_arity 1; my $s = shift; FP::_::MutableArray->new_from_array([@$s]) } # emptyness constructor that works for subclassing (using singletons # for performance (perhaps)) my %null; sub null { my $proto = shift; my $class = ref($proto) || $proto; $null{$class} ||= $class->new_from_array([]) } sub constructor_name { "purearray" } our $pure_warned = 0; sub pure { @_ == 1 or fp_croak_arity 1; my $a = shift; carp "is already pure" unless $pure_warned++; $a } sub unsafe_mutable { @_ == 1 or fp_croak_arity 1; my $a = shift; Internals::SvREADONLY $_, 0 for @$a; Internals::SvREADONLY @$a, 0; require FP::MutableArray; # cost? bless $a, "FP::_::MutableArray" } _END_; # Chj::NamespaceCleanAbove FP::Interfaces::implemented qw( FP::Abstract::Pure FP::Abstract::Sequence FP::Abstract::Equal FP::Abstract::Show); } 1