http://qs321.pair.com?node_id=380421

Let's pretend: we're writing a markup-translation backend. The front end has taken the input text and transformed it into a bunch of paragraphs, each one a list of (style, content) pairs. For instance, a paragraph might look like

my @test_data = ( ['none', 'The quick brown '], ['bold', 'fox'], ['none', ' jumped '], ['ital', 'over'], ['none', ' the lazy '], ['bold', 'dog'], ['none', '.'] );
Our task is to generate HTML from these paragraphs.

One obvious WTDI is to build a dispatch table of translation functions:

my %handlers = ( 'none' => sub {shift}, 'bold' => \&wrap_with_b_tags, 'ital' => \&wrap_with_i_tags, 'para' => \&wrap_with_p_tags, ); sub build_para { my ($data) = @_; my $text = ''; for (@$data) { $text .= $handlers{$_->[0]}->($_->[1]); } return $handlers{'para'}->($text); }
That's all well and good, but those wrap_with_foo_tags functions are all going to be variations on a theme. That's going to suck if we ever want to extend them (to allow for CSS classes, for instance). What we'd like to be able to do is something like
sub wrap_with_html { my ($tag, $text) = @_; return "<$tag>$text</$tag>"; } my %handlers = ( 'none' => sub {shift}, 'bold' => \&wrap_with_html('b', # partial funcall 'ital' => \&wrap_with_html('i', 'para' => \&wrap_with_html('p', );
but of course that won't work. Will it?

Enter currying. Currying is basically partial application of a function: instead of supplying all of a function's arguments and getting a result back, you supply part of a function's arguments and get a function back. When you apply that curried function to the rest of the arguments, you get the result you expected.

educated_foo has a pretty clever currying implementation here, but for my purposes this should be sufficient:

sub curry { my ($func, @args) = @_; return sub { my (@rest) = @_; &$func(@args, @rest); } } # Currying: sub foo { my ($x, $y) = @_; print "$x + $y = ", $x + $y, "\n"; } my $plus_five = &curry(\&foo, 5); &$plus_five(3);
It's not pretty, nor is it especially safe, but it works.

Now we can have our cake and eat it, too: a pleasantly general wrap_with_html function, a dispatch table full of specialized functions (which, I might add, we can generate from a config file if we so desire), a distinct lack of superfluous wrapper code, and a bunch of coderefs to confuse the newbies. (Okay, that last is a bug, not a feature. That's the major drawback to currying: it's not easy to understand from a procedural point of view.) Our full code looks like this:

sub curry { my ($func, @args) = @_; return sub { my (@rest) = @_; &$func(@args, @rest); } } my %handlers = ( 'none' => sub {shift}, 'bold' => &curry(\&wrap_with_html, 'b'), 'ital' => &curry(\&wrap_with_html, 'i'), 'para' => &curry(\&wrap_with_html, 'p'), ); sub wrap_with_html { my ($tag, $text) = @_; return "<$tag>$text</$tag>"; } sub build_para { my ($data) = @_; my $text = ''; for (@$data) { $text .= $handlers{$_->[0]}->($_->[1]); } return $handlers{'para'}->($text); } my @test_data = ( ['none', 'The quick brown '], ['bold', 'fox'], ['none', ' jumped '], ['ital', 'over'], ['none', ' the lazy '], ['bold', 'dog'], ['none', '.'] ); print &build_para(\@test_data);

I should point out that this is a bit of a toy example: it might be better to use qred regexes in a lookup table instead, for instance. I didn't want to clutter the code with more complexity than I needed; but the nice thing about this approach is that if you ever have to make wrap_with_html more complex, you don't really have to change anything else.

--
F o x t r o t U n i f o r m
Found a typo in this node? /msg me
% man 3 strfry