diff options
-rwxr-xr-x | perl/functional/main.pl | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/perl/functional/main.pl b/perl/functional/main.pl new file mode 100755 index 0000000..2198cf3 --- /dev/null +++ b/perl/functional/main.pl @@ -0,0 +1,227 @@ +#!/usr/bin/env perl + +# ----------------------------------------------------------------------------- +# +# main.pl - A Functional Programming Starting Point for Perl Projects +# +# ----------------------------------------------------------------------------- +# This script serves as a template for new Perl projects, emphasizing a +# functional, pragmatic style. It includes standard setup, custom functional +# utilities, and an architectural pattern for separating pure logic from + +# impure I/O, inspired by The Elm Architecture. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# SECTION 1: STANDARD SETUP (PRAGMAS) +# ----------------------------------------------------------------------------- +# These pragmas enforce modern, safer Perl conventions. +# +# - strict: Enforces rules to prevent common mistakes, like typos in variable +# names. It requires you to declare variables with `my`. +# - warnings: Catches potential problems and questionable coding practices, +# printing warnings to STDERR. +# - feature: Enables modern language features. 'say' is a newline-aware +# print. 'signatures' provides clean subroutine argument lists. +# ----------------------------------------------------------------------------- +use strict; +use warnings; +use feature qw(say signatures); + +# List::Util is a core module (no extra install needed). It provides highly +# optimized list-processing functions. We use `reduce` (also known as `foldl`) +# as a fundamental building block for other functions. +use List::Util 'reduce'; + +# For demonstrating immutability on complex data structures. +# In a real project, you would `cpanm Readonly` to install this. +# For this example, we will just use convention. +# use Readonly; # Uncomment if you have the Readonly module installed. + + +# ----------------------------------------------------------------------------- +# SECTION 2: FUNCTIONAL UTILITIES +# ----------------------------------------------------------------------------- +# These are custom-built, higher-order functions that form the core of a +# functional toolkit. They operate on other functions to create new, more +# powerful functions. +# ----------------------------------------------------------------------------- + +# +# compose(@funcs) +# +# Takes a list of functions and returns a new function that applies them +# right-to-left. +# +# Example: compose($f, $g, $h)->($x) is equivalent to $f($g($h($x))). +# +# This is useful for building complex logic by chaining together simple, +# reusable functions in a readable, mathematical style. +# +sub compose(@funcs) { + return sub ($initial_value) { + return reduce { $a->($b) } $initial_value, reverse @funcs; + }; +} + +# +# pipe(@funcs) +# +# Takes a list of functions and returns a new function that applies them +# left-to-right. This is often more intuitive than compose for data +# processing workflows. +# +# Example: pipe($f, $g, $h)->($x) is equivalent to $h($g($f($x))). +# +# This reads like a sequence of steps, e.g., "take x, then do f, then do g". +# +sub pipe(@funcs) { + return sub ($initial_value) { + return reduce { $b->($a) } $initial_value, @funcs; + }; +} + +# +# curry($func, @args) +# +# Transforms a function that takes multiple arguments into a function that +# takes some of those arguments now, and the rest later. +# +# Example: +# my $add = sub ($x, $y) { $x + $y }; +# my $add_five = curry($add, 5); # Creates a new function that adds 5. +# my $result = $add_five->(10); # Result is 15. +# +# This is excellent for creating specialized versions of general functions. +# +sub curry($func, @args) { + return sub { + return $func->(@args, @_); + }; +} + +# ----------------------------------------------------------------------------- +# SECTION 3: ARCHITECTURE - PURE LOGIC VS IMPURE I/O +# ----------------------------------------------------------------------------- +# This section demonstrates a pattern for structuring applications to achieve +# a clean separation of concerns. All core logic is contained in a single, +# pure `update` function. All I/O (printing, reading, etc.) is handled +# outside of it. This makes the core logic extremely easy to test and reason +# about. +# +# This pattern is a simplified version of The Elm Architecture. +# - Model: The state of your application. +# - Msg: A description of something that happened (e.g., user input). +# - update: A pure function that computes the new state. +# ----------------------------------------------------------------------------- + +# +# update($msg, $model) +# +# This is the PURE core of the application. +# It takes a message (describing an event) and the current state (the model), +# and it returns the NEW state. +# +# Crucially, this function does NO I/O. It doesn't print, read files, or have +# any other "side effects". Its only job is to compute the next state. +# Because it's pure, you can call it with the same arguments 100 times and +# always get the same result, making it perfectly predictable and testable. +# +sub update($msg, $model) { + # Create a mutable copy to work with. The original $model is not changed. + my %new_model = %{$model}; + + if ($msg->{type} eq 'INCREMENT') { + $new_model{counter} += 1; + } + elsif ($msg->{type} eq 'ADD_X') { + $new_model{counter} += $msg->{payload}; + } + elsif ($msg->{type} eq 'SET_NAME') { + $new_model{name} = $msg->{payload}; + } + + # Return a reference to the new model. + # Note: A more robust implementation would ensure a deep, immutable copy. + # For many scripts, convention is sufficient. + return \%new_model; +} + + +# ----------------------------------------------------------------------------- +# SECTION 4: MAIN EXECUTION BLOCK (The "IMPURE" Runtime) +# ----------------------------------------------------------------------------- +# This is the imperative shell that orchestrates the application. It handles +# all I/O and calls the pure `update` function to process state changes. +# ----------------------------------------------------------------------------- +sub main() { + say "--- Functional Perl Starter ---"; + + # --- Part 1: Demonstrating Built-in Functional Operators --- + say "\n1. Demonstrating map and reduce (from List::Util)"; + my @numbers = (1 .. 5); + + # `map` applies a function to each element of a list, returning a new list. + my @doubled = map { $_ * 2 } @numbers; + say "Original: @numbers"; + say "Doubled: @doubled"; + + # `reduce` combines all elements of a list into a single value. + my $sum = reduce { $a + $b } 0, @numbers; # Start with an accumulator of 0 + say "Sum: $sum"; + + + # --- Part 2: Demonstrating Custom Functional Utilities --- + say "\n2. Demonstrating pipe, compose, and curry"; + + my $add_one = sub ($n) { $n + 1 }; + my $mult_two = sub ($n) { $n * 2 }; + my $sub_three = sub ($n) { $n - 3 }; + + # pipe: (5 + 1) * 2 - 3 = 9 + my $pipeline_op = pipe($add_one, $mult_two, $sub_three); + say "pipe(5): " . $pipeline_op->(5); + + # compose: 5 * 2 + 1 - 3 = 8 + my $compose_op = compose($sub_three, $add_one, $mult_two); + say "compose(5): " . $compose_op->(5); + + # curry + my $add = sub ($x, $y) { $x + $y }; + my $add_ten = curry($add, 10); + say "curry(add 10 to 7): " . $add_ten->(7); # 17 + + + # --- Part 3: Demonstrating the Pure/Impure Architecture --- + say "\n3. Demonstrating The Elm-style Architecture"; + + # The initial state of our application. + my $model = { + counter => 0, + name => "World", + }; + + say "Initial Model: counter = $model->{counter}, name = $model->{name}"; + + # We simulate a sequence of events (messages). + my @events = ( + { type => 'INCREMENT' }, + { type => 'INCREMENT' }, + { type => 'SET_NAME', payload => 'Perl' }, + { type => 'ADD_X', payload => 10 }, + ); + + # The "Runtime Loop": Process each event through the pure `update` function. + # The runtime's job is to call `update` and then perform I/O (like `say`). + for my $msg (@events) { + say " -> Processing event: $msg->{type}"; + $model = update($msg, $model); # Calculate new state + } + + say "Final Model: counter = $model->{counter}, name = $model->{name}"; + + return 0; +} + +# Run the main subroutine. +exit main(); |