NAME

FP::Struct - classes for functional perl

SYNOPSIS

use FP::Predicates qw(is_array maybe);

use FP::Struct 'FPStructExample::Foo' =>
        ["name", # accept any value
         [maybe (\&is_array), "animals"], # accept arrays or undef
        ]
        # => "Baz", "Buzz" # optional superclasses
         ;

# creates a constructor new that takes positional arguments and
# copies them to a hash with the keys "name" and "animals". Also,
# sets @Bar::ISA to ("Baz", "Buzz") if the '#' is removed. [ ] around
# "Baz", "Buzz" are optional.  If an array is given as a field
# declaration, then the first entry is a predicate that receives the
# value in question, if it doesn't return true then an exception is
# thrown.

is( new FPStructExample::Foo ("Tim")->name, "Tim" );
eval {
     new FPStructExample::Foo ("Tim", 0)
};
like $@, qr/^unacceptable value for field 'animals': 0 /;
is (new FPStructExample::Foo (undef, ["Struppi"])->animals->[0], "Struppi");
is (new_ FPStructExample::Foo (animals => ["Struppi"])->animals->[0], "Struppi");


# Usually preferred alternative: define the struct from within the
# package:

# a mixin package, if this weren't defined at the time of 'use
# FP::Struct' below, it would try to load Hum.pm
package FPStructExample::Hum {
    sub hum {
        my $s = shift;
        $s->name." hums ".$s->a." over ".$s->b
    }
}
package FPStructExample::Hah {
    use FP::Struct ["aa"];
    _END_
}

package FPStructExample::Bar2 {

  use Chj::TEST; # the TEST sub will be removed from the package upon
                 # _END_ (namespace cleaning)
  use FP::Struct ["a","b"] => "FPStructExample::Foo",
                             "FPStructExample::Hum",
                             "FPStructExample::Hah";
  sub div {
     my $s = shift;
     $$s{a} / $$s{b}
  }
  TEST { FPStructExample::Bar2->new_(a => 1, b => 2)->div } 1/2;
  _END_ # generate accessors for methods of given name which don't
        # exist yet *in either Bar or any super class*. (Does that
        # make sense?)
        # and export the constructors to the packages given here:
     "main"; 
}

package main {
    use FP::Equal 'is_equal';

    my $bar = new FPStructExample::Bar2 ("Franz", ["Barney"], "some aa", 1,2);
    # same thing, but with sub instead of method call interface:
    my $baz = FPStructExample::Bar2::c::Bar2 ("Franz", ["Barney"], "some aa", 1,2);
    # or:
    #FPStructExample::Bar2::constructors->import;
    # or in fact, we already exported them via _END_("main")
    my $baz = Bar2 ("Franz", ["Barney"], "some aa", 1,2);

    is $bar->div, 1/2;

    is(Bar2_(a => 1,b => 2)->div, 1/2);
    is(FPStructExample::Bar2::c::Bar2_(a => 1, b => 2)->div, 1/2);
    is(new__ FPStructExample::Bar2({a => 1,b => 2})->div, 1/2);
    is(unsafe_new__ FPStructExample::Bar2({a => 1,b => 2})->div, 1/2);
    # NOTE: unsafe_new__ returns the argument hash after checking and
    # blessing it, it doesn't copy it! Be careful. `new__` does copy it.

    is $bar->b_set(3)->div, 1/3;

    use FP::Div 'inc';
    is $bar->b_update(\&inc)->div, 1/3;

    is $bar->hum, "Franz hums 1 over 2";

    is Chj::TEST::run_tests("FPStructExample::Bar2")->successes, 1;
    is (FPStructExample::Bar2->can("TEST"), undef);
    # ^ it was removed by namespace cleaning

    # Meta information: what was given in the field definitions,
    # for the class and all FP::Struct based super classes:
    my @fd = FP::Struct::all_fields(["FPStructExample::Bar2"]);
    my @fieldnames = map { ref $_ ? $_->[1] : $_ } @fd;
    is_equal \@fieldnames,
             [ 'name', 'animals', 'aa', 'a', 'b' ];
    # Just the field names, directly:
    is_equal [ FP::Struct::all_field_names(["FPStructExample::Bar2"]) ],
             [ 'name', 'animals', 'aa', 'a', 'b' ];
}

DESCRIPTION

Create functional setters (i.e. setters that return a copy of the object so as to leave the original unharmed), take predicate functions (not magic strings) for dynamic type checking, simpler than Class::Struct.

Also creates constructor methods: new that takes positional arguments, new_ which takes name => value pairs, new__ which takes a hash with name => value pairs as a single argument, and unsafe_new__ which does the same as new__ but reuses the given hash (unsafe if the latter is modified later on).

Also creates constructor functions (i.e. subroutine instead of method calling interface) Foo::Bar::c::Bar() for positional and Foo::Bar::c::Bar_() for named arguments for package Foo::Bar. These are also in Foo::Bar::constructors:: and can be imported using (without arguments, it imports both):

Foo::Bar::constructors->import(qw(Bar Bar_));

_END_ does namespace cleaning: any sub that was defined before the use FP::Struct call is removed by the _END_ call (those that are not the same sub ref anymore, i.e. have been redefined, are left unchanged). This means that if the use FP::Struct statement is put after any other (procedure-importing) 'use' statement, but before the definition of the methods, that the imported procedures can be used from within the defined methods, but are not around afterwards, i.e. they will not shadow super class methods. (Thanks to Matt S Trout for pointing out the idea.) To avoid the namespace cleaning, write _END__ instead of _END_. Both of these optionally take any number of packages, to which they will export the constructors.

See FP::Predicates for some useful predicates (others are in the respective modules that define them, like is_pair in FP::List).

UTILITY BASE CLASSES

PURITY

It is recommended to use FP::Abstract::Pure as a base class. This means objects from classes based on FP::Struct are automatically treated as pure by is_pure from FP::Predicates.

If $FP::Struct::immutable is true (default), then if FP::Abstract::Pure is inherited the objects are made immutable to ensure purity.

FP::Struct specific

To avoid having to implement some protocols manually, some base classes are provided to auto-derive those protocols automatically: <FP::Struct::Show>, <FP::Struct::Equal>. They use the knowledge that <FP::Struct> has about the fields in the class to make the protocol consider all the fields automatically.

:defaults

If ":defaults" is given as a base class, it is expanded to those base classes that should always work and should always be useful. Currently, these are <FP::Struct::Show>, <FP::Struct::Equal>.

ALSO SEE

FP::Abstract::Pure, <FP::Struct::Show>, <FP::Struct::Equal>

NOTE

This is alpha software! Read the status section in the package README or on the website.