Lady_Aleena has asked for the wisdom of the Perl Monks concerning the following question:

Please forgive me my brothers and sisters for this fourth node about my HTML::Element module. (1st, 2nd, 3rd with this tangentially related with a GitHub gist to hold the entire module so I do not copy it over and over again here.)

To the point, I am using too many (nested) coderefs. I have found going too deep with coderefs can have some nasty effects, such as losing variable values if one goes too deep. I try to have all my data munged before I even begin using my HTML element functions, however, if my variables become undefined at certain depths, it would be difficult to keep from mixing my logic from my display.

Initially, I had only two functions I use often with coderefs, however, there are six (or more) new tags which coderefs seem to be the only way to go. I am to the point of thinking about adding some arbitrary rules, but I am trying to avoid it.

Here is a sample function. (body can be replaced with div, article, section, aside, nav, header, footer.)

sub body { my ($tab,$code,$opt) = @_; my $tag = 'body'; my $open = open_tag($tag,$opt,[@ics,@java]); line($tab,"<$open>"); &$code; line($tab,"</$tag>"); }

The possible (and abbreviated) usage could be...

body( sub { header( sub { ... some header code for the body ... }); ... some random code in the body ... article( sub { header( sub { ... some header code for the section... }); ... some random code in the article ... section( sub { header( sub { ... some header code for the section ... }); ... some random code in the section ... aside( sub { header( sub { ... some header code for the aside ... }); ... some random code in the aside... footer( sub { ... some footer code for the aside ... }); }); ... some random code in the section... footer( sub { ... some footer code for the section... }); }); ... some random code in the article... section( sub { header( sub { ... some heading code for this section... }); ... some random code for this section... footer( sub { ... some footer code for this section... }); }); aside( sub { header( sub { ... some header code for this aside ... }); ... some random code for this aside ... footer( sub { ... some footer code for this aside ... }); }); footer( sub { ... some footer code for the article ... }); }); footer( sub { ... some footer code for the body ... }); });

Some of those arbitrary rules I was thinking about could lead me to rewrite all but header and footer to look something like...

sub body { my ($tab,$opt) = @_; my $tag = 'body'; my $open = open_tag($tag,$opt,[@ics,@java]); line($tab,"<$open>"); header($tab + 1, @{$opt->{header}}) if $opt->{header}; &{$opt->{contents}}; footer($tab + 1, @{$opt->{footer}}) if $opt->{footter}; line($tab,"</$tag>"); }

The above could make the code look something like the following, however there are still too many coderefs.

body( header => sub { ... header code for body ... }, footer => sub { address( sub { ... adress code for the footer of the body ... }); ... footer code for body ... }, contents => sub { ... random code for body ... article( header => sub { ... header code for article ... }, footer => sub { ... footer code for article ... }, contents => sub { ... random code for article ... section( header => sub { ... header code for section ... }, footer => sub { ... footer code for section ... }, contents => sub { ... random code for section ... aside( header => sub { ... header code for aside ... }, footer => sub { ... footer code for aside ... }, contents => sub { ... random code for aside ... }, ); }, ); ... random code for article ... section( header => sub { ... header code for section ... }, footer => sub { ... footer code for section ... }, contents => sub { ... random code for section ... }, ); ... random code for article ... aside( header => sub { ... header code for aside ... }, footer => sub { ... footer code for aside ... }, contents => sub { ... random code for aside ... }, ); ... random code for article ... }, ); }, );

As you can see, way too many coderefs to get one lost inside a script. I am a little lost right now after looking at it too long. I do not think any of my pages would ever get as complex as shown above, however, I am hoping to one day release this to CPAN for others to possibly use.

Have a cookie and a very nice day!
Lady Aleena

Replies are listed 'Best First'.
Re: I'm drowning in coderefs in my HTML element module
by kcott (Archbishop) on Apr 30, 2013 at 10:04 UTC

    G'day Lady Aleena,

    My first thought on this was to have a single, recursive routine that received the tag name as an argument. I had a very brief look at all the threads that had gone before but I don't have time to read them in detail. Consider the following a technique, rather than a solution, which you might be able to integrate into what you have already.

    Here's pm_la_html.pl:

    #!/usr/bin/env perl use strict; use warnings; my $indent = ' ' x 4; my $web_page = [ [ html => { attr => { lang => 'en' }, content => [ [ head => { content => [ [ title => { content => 'Some Title', }, ], ], }, ], [ body => { attr => { id => 'top' }, content => [ [ header => { }, ], [ article => { }, ], [ footer => { }, ], ], }, ], ], }, ], ]; markup($web_page); sub markup { my ($element_array, $depth) = @_; $depth ||= 0; for my $element (@$element_array) { my ($tag, $data) = @$element; print $indent x $depth, "<$tag"; if (exists $data->{attr}) { for my $key (keys %{$data->{attr}}) { print " $key=\"$data->{attr}{$key}\""; } } if (exists $data->{content}) { print ">\n"; if (ref $data->{content}) { markup($data->{content}, $depth + 1); } else { print $indent x ($depth + 1), "$data->{content}\n"; } print $indent x $depth, "</$tag>\n"; } else { print " />\n"; } } return; }

    Here's the output:

    $ pm_la_html.pl <html lang="en"> <head> <title> Some Title </title> </head> <body id="top"> <header /> <article /> <footer /> </body> </html>

    -- Ken

Re: I'm drowning in coderefs in my HTML element module
by Arunbear (Prior) on Apr 30, 2013 at 17:20 UTC
    Template::Declare also uses anon subs to create HTML, so you could look at its source to see how it does this. Having said that, Template::Declare seems usage-wise much less verbose and feature-wise much more powerful than your module, so it's worth considering if it can fit your needs.
Re: I'm drowning in coderefs in my HTML element module
by hdb (Monsignor) on Apr 30, 2013 at 08:30 UTC

    Your formatting standards are probably not optimal for this situation. I have taken the liberty to reformat it somewhat. Also I would propose to change the order always to header - content - footer which makes it a bit more readible as well (unless this has an impact on the output?).

    You can also avoid deep nesting by naming a few of the code refs and define them separately.

    Here is an illustration of what I mean, I hope this helps.

    # non-standard formatting ############################################ +############################# # always put footer at the end (assuming order does not matter?) body( header => sub { hgroup( sub { }); }, contents => sub { article( header => sub { }, contents => sub { section( header = +> sub { }, contents = +> sub { aside( header => sub { }, + contents => sub { }, + footer => sub { }, ); }, footer = +> sub { }, ); section( header = +> sub { }, contents = +> sub { }, footer = +> sub { }, ); aside( header = +> sub { }, contents = +> sub { }, footer = +> sub { }, ); }, footer => sub { }, ); }, footer => sub { address( sub { }); }, ); # alternative ######################################################## +############################# # remaining super-structure body( header => sub { hgroup( sub { }); }, contents => sub { article( header => sub { }, contents => \&contents1, footer => sub { }, ); }, footer => sub { address( sub { }); }, ); # delegated content sub contents1 { section( header => sub { }, contents => sub { aside( header => sub { }, contents => sub { }, footer => sub { }, + ); }, footer => sub { }, ); section( header => sub { }, contents => sub { }, footer => sub { }, ); aside( header => sub { }, contents => sub { }, footer => sub { }, ); }