about summary refs log tree commit diff stats
path: root/perl/functional/main.pl
blob: 2198cf37ec30872c2f15c93977d1fe08031d7614 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
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();