Functional Logo Perl                  
history | edit

Functional programming in Perl

This project aims to make it easier to reduce the number of places in Perl programs where side effects are used, by providing facilities like data structures to enable it and tutorials and introductions to show good ways to go about it.

Side effects (mutation and input/output), unless they are contained locally (transparent to the user of a subroutine/method/API) are not part of the method/subroutine calling interface but are implicit (hopefully at least documented), and such a call has lingering effects, possibly at a distance. This makes tracking down bugs more difficult, and can hinder the reuse of program parts in newly combined ways. Also, code that uses side effects may not be idempotent (hence produce failures) or be calculating different values when re-run, which prevents its use in an interactive way, like from a read-eval print loop or debugger, and makes writing tests more difficult.

Functional programming is often associated with strong static typing (as in Haskell or Scala). Those two complement each other because the side-effect free model simplifies the description of what a piece of code does via its types, and types can guarantee that a particular piece of code is pure. But the same advantages that functional programs have for machines to reason about are also valid for humans. Thus you can still benefit from functional programming in a dynamically typed language like Perl--and you get some additional interactivity benefits on top.

Contents

  • 1. Examples
  • 2. Status: alpha
  • 3. Parts
  • 4. Documentation
  • 5. Dependencies
  • 6. Installation
  • 6.1. From CPAN
  • 6.2. From the Git repository
  • 7. Reporting bugs, finding help, contributing
  • 1. Examples

    Work more easily with sequences:

    use Test::More;
    use FunctionalPerl ":all"; # includes autoboxing (methods on arrays work)
    
    is [2, 3, 4]->reduce(\&add), 9; # the `sum` method does the same
    is [2, 3, 4]->map(\&square)->sum, 29;
    

    There are short constructor functions like list for non-native data structures; equal knows how to deal with all comparable data structures:

    use FP::Equal;
    ok equal( ["a".."z"]->chunks_of(2)->take(3)->list,
              list(purearray('a', 'b'), purearray('c', 'd'), purearray('e', 'f')) );
    

    purearrays (FP::PureArray) are arrays but immutable to prevent accidental mutation. You could request mutable ones:

    ok equal( ["a".."z"]->chunks_of(2)->take(3)->map(the_method "array")->array,
              [['a', 'b'], ['c', 'd'], ['e', 'f']] );
    

    Make some XML document:

    # Generate functions which construct PXML objects (objects that
    # can be serialized to XML) with the names given her as the XML
    # tag names:
    use PXML::Tags qw(myexample protocol-version records record a b c d);
    # The functions are generated in all-uppercase so as to minimize
    # the chances for naming conflicts and to let them stand apart.
    
    is RECORD(A("hi"), B("<there>"))->string,
       '<record><a>hi</a><b>&lt;there&gt;</b></record>';
    

    Now create a bigger document, with its inner parts built from external inputs:

    MYEXAMPLE(
        PROTOCOL_VERSION("0.123"),
        RECORDS(
            csv_file_to_rows($inpath, {eol => "\n", sep_char => ";"})
            # skip the header row
            ->rest
            # map rows to XML elements
            ->map(sub {
                      my ($a,$b,$c,$d) = @{$_[0]};
                      RECORD(A($a), B($b), C($c), D($d))
                  })))
        # print XML document to disk
        ->xmlfile($outpath);
    

    The MYEXAMPLE document above is built lazily: csv_file_to_rows returns a lazy list of rows, ->rest causes the first CSV row to be read and dropped and returns the remainder of the lazy list, ->map returns a new lazy list which is passed as argument to RECORDS, which returns a PXML object representing a 'records' XML element, that is then passed to MYEXAMPLE which returns a PXML object representing a 'myexample' XML element. PXML objects come with an xmlfile method which serializes the document to a file, and only while it runs, when it encounters the embedded lazy lists, does it walk those evaluating the list items one at a time and dropping each item immediately after printing. This means that only one row of the CSV file needs to be held in memory at any given point.

    (Note that the example still assumes that steps have been taken so that the CSV file doesn't change until the serialization step has completed, otherwise functional purity is broken; the responsibility to ensure this assumption is left to the programmer (see howto (Pure functions versus I/O an..) for more details about this).)

    The core idea of functional programming is the avoidance of mutation. In the absense of mutation, a value, once calculated, stays the same, it is immutable. For example numbers: once a number is calculated you can't modify it in place; if you have multiple variables holding the same number, you can't (on purpose or accidentally) change them both at the same time:

    my $x = 100;
    my $y = $x;
    $x++;
    is $y, 100; # still true, the number itself didn't change, only the variable
    

    There is no number operation that modifies numbers in place, they all return a new number instance. The same isn't true for most other values in Perl; they let you modify their internal contents without giving you a new reference, and it's usually the default way how things are done. Strings and sometimes arrays are often copied instead, which for large instances becomes inefficient. Setters on objects usually just modify an object in place (they mutate it). This project helps both with efficiency (minimizing copying) and ergonomy (automatically creates functional setters for class fields). Here's an example with a simple class to show the difference.

    Code that doesn't mutate (pure functions or methods) can be combined easily into new functions, which are still pure and thus can be further combined. compose takes any number of function references (coderefs) and returns a new function (coderef) that applies those functions to its argument in turn (it is a combinator function, there are more inFP::Combinators):

    # The function that adds all of its arguments together then
    # squares the result:
    *square_of_the_sum = compose \&square, \&add;
    is square_of_the_sum(10,20,2), 1024;
    
    # The same but takes the input items from a list instead of
    # multiple function arguments:
    *square_of_the_sequence_sum = compose(\&square, the_method "sum");
    is square_of_the_sequence_sum(list(2, 3)), 25;
    

    Functional programming matters more in the large--with small programs it's easy to keep all places where mutation happens in the head, wheras with large ones the interactions can become unwieldy.

    See the examples page for more examples.

    If you'd like to see a practical step-by-step introduction, read the intro. Or check the The Perl Weekly Challenges, #113 blog post, it introduces many features given concrete tasks, for which various standard Perl solutions have also been written.

    For an index into all modules, see the "see also" section in FunctionalPerl.

    2. Status: alpha

    This project is in alpha status because:

    There is a lot that still needs to be done, and it depends on the author or other people be able and willing to invest the time.

    (An approach to use this project while avoiding breakage due to future changes could be to add the functional-perl Github repository as a Git submodule to the project using it and have it access it via use lib. Tell if you'd like to see stable branches of older versions with fixes.)

    3. Parts

    4. Documentation

    It probably makes sense to look through the docs roughly in the given order, but if you can't follow the presentation, skip to the intro, likewise if you're bored skip ahead to the examples and the howto/design documents.

    Please ask me if you'd like to meet up in London, Berlin or Switzerland to get an introduction in person.

    5. Dependencies

    (Todo: should all of the above be listed in PREREQ_PM in Makefile.PL?)

    You can run meta/install-development-dependencies-on-debian to get those installed if you're on a Debian system.

    6. Installation

    6.1. From CPAN

    Use your preferred CPAN installer, for example: cpan FunctionalPerl. Note that this one installs the "runtime recommends" dependencies as well, which is a lot (like e.g. DBI), so you may want to look through the Makefile.PL or the list below and install what you can from your Linux distribution or other binary package repository first. Or if you don't want the recommends, set recommends_policy in $ENV{HOME}/.cpan/CPAN/MyConfig.pm to 0, or install the cpanminus tool and run cpanm FunctionalPerl which skips recommends unless you pass the --with-recommends option.

    6.2. From the Git repository

    git clone https://github.com/pflanze/functional-perl.git
    cd functional-perl
    
    # To get the latest release, which is 2 commits behind master:
    git checkout -b v0_72_76 v0.72.76
    
    # To verify the same against MitM attacks:
    gpg --recv-key 04EDB072
    git tag -v v0.72.76
    # You'll find various pages in search engines with my fingerprint,
    # or you may find a trust path through one of the signatures on my
    # older key 1FE692DA, that this one is signed with.
    

    The bundled scripts modify the library load path to find the files locally, thus no installation is necessary. All modules are in the lib/ directory, export PERL5LIB=path/to/functional-perl/lib is all that's needed.

    To install, run the usual perl Makefile.PL; make test && make install.

    (The repository might be split into producing several separate CPAN packages (or even repositories?) in the future, thus don't rely too much on the installation process continuing to work the way it is right now.)

    7. Reporting bugs, finding help, contributing