Hacker News new | ask | show | jobs
by Furiosity 2748 days ago
Perl (5 or 6) doesn't try to treat a programming language as a natural language. Instead it tries to allow the structure in which the author's brain defines the problem to be able to match the structure in which the code of the program defines the solution. It is linguistic in the sense that it tries to follow the normal flow of human language as a form of communication, allowing skills learned for human communication to be re-used for communicating via source code--not in a natural, human language (still in a computer language) but without necessarily having to pivot the problem in to something more natural for computers first.

Source code is instructions for two audiences: A human, and a computer. The computer doesn't care what the syntax is, but the human does. It is an ongoing challenge to write code in a manner which effectively and concisely communicates to other humans, including your future self. This is a thing at which Perl5 and Perl6 both excel, Perl6 more so IMO (although there is not yet enough history to prove it).

It is true that one can easily write unmaintainable spaghetti in Perl5. One can write unmaintainable spaghetti in any language, but Perl5 makes it somewhat easier to do it by mistake--this is an aspect of the same versatility which gives it its expressive power, which in turn is what makes it possible to communicate things using Perl more effectively (in the same space) than with many other computer languages. You have to employ some discipline not to sound like a gibbering lunatic. This is not much different from human languages: if you have ever read English 101 papers you will find some of them sadly similar to Perl5 scripts written by similarly undisciplined authors.

In contrast to Perl5, Perl6 makes it far easier to avoid accidental spaghetti and does so without sacrificing any of the linguistic expressiveness that makes Perl generally so useful.

2 comments

> It is true that one can easily write unmaintainable spaghetti in Perl5.

This was something that surprised me when I first learned about Perl 5 in college. Most people in the internet would parrot it as a "write-only" language and yet I've always being capable of getting the whole picture of a code snippet posted on Perl Monks, Reddit, Stack Overflow, etc. Granted I've always steered away from golf code which obviously has its place but shouldn't be mistaken as being the "de facto way" of writing Perl 5 or Perl 6 for that matter.

>In contrast to Perl5, Perl6 makes it far easier to avoid accidental spaghetti and does so without sacrificing any of the linguistic expressiveness that makes Perl generally so useful.

Can you give some examples of that point, w.r.t. Perl6?

I'm not sure if I've nailed what the GP was speaking of or what you're looking for but anyway I do think the following is illustrative of the linguistic evolution P6 represents.

Earlier this year at perlmonks (perhaps the top resource along with stackoverflow for experienced perl folk answering questions) a poster wanted solutions to a fairly basic operation:[1]

> I need a function which will filter a nested hash, removing any fields I'm not interested in.

Their test data had a source that began:

    my $source = {
        f1 => 'garbage',
        f2 => 'more garbage',
        f3 => 'important data',
        f4 => {
            this => 'sub hash',
            is   => 'garbage'
        },
        f5 => {
            f6 => 'more important data',
and a filter that began:

    my $filter = {
        f3 => 1,
        f5 => {
            f6 => 1,
After a few days the person who asked the question summarized the community's P5 answers.[2]

The first one listed (slightly trimmed):

    #! /usr/bin/env perl

    use strict;
    use warnings;

    use Carp;

    use Deep::Hash::Utils qw(reach nest deepvalue);

    sub hash_filter_recursive {
      my $source = shift;
      my $filter = shift;

      my %output;

      foreach ( keys %$filter ) {
        if ( exists $source->{$_} ) {

          if ( ref $filter->{$_} eq 'HASH' ) {
            croak "bad filter: on '$_', expected HASH\n"
              unless ( ref $source->{$_} eq 'HASH' );

            $output{$_} = hash_filter_recursive( $source->{$_}, $filter->{$_} );
          }
          else {
            $output{$_} = $source->{$_};
          }

        }
      }

      return \%output;
    }
(This appears to include a modicum of input validation.)

Here was my P6 answer, including the call to invoke the routine (which I omitted from the P5):[3]

    sub infix:<landr> ($l, $r) { $l and $r };
    say $filter «landr» $source
Even though this uses a parallel operation which will one day optionally be automatically mapped to multiple cores to run faster, I'm pretty sure this is currently slower than the P5 solutions.

And I didn't bother with any validation that it was indeed being passed a nested hash.

But it makes a point. While P6 can typically do similar things to P5, it often turns out easier to do many things at a much higher level, which leads to less spaghetti.

----

[1] https://www.perlmonks.org/?node_id=1215517

[2] https://www.perlmonks.org/?node_id=1215736

[3] https://www.perlmonks.org/?node_id=1216274

Note that there was no need to actually create an infix operator, as you can use any code as an infix operator if you surround it with `[&( )]`.

    say $filter «[&(  -> $l, $r { $l and $r }  )]» $source
Golfing it down a bit, the shortest I was able to get was

    say $filter «[&(   &[and]   )]» $source;
The `&[and]` references the infix `and` operator.

I think the reason the following doesn't work that way is that `and` is too thunky.

    say $filter «and» $source;
    say $filter «[and]» $source; # identical to previous line
(Note that `and` is supposed to be thunky, so that isn't a bug.)

---

I personally would write this:

    sub infix:<l-when-r> -> $_, $r {
      # note that the value to be matched with must be in $_
      # prior to using the `when` keyword.
      # it was done in the signature for conciseness

      $_ when $r
    }
Then the conditions can be code objects, regexes (which are a form of code object in Perl6), or literals. (I tried to get it to work with bare Junctions, but I was unable to get the `«»` part to pass it into the operator.)

It does mean that you have to use `True` rather than `1`.

    my $filter = {
        f2 => True,
        f3 => *.contains('data'),
        f5 => {
            f6 => /data/,
        },
        f10 => * == (0 | 1 | 42), # only match these values
        f11 => 42, # only match something equal to 42
    }

    say $source «l-when-r» $filter;
Thanks for the reply. Definitely looks like P6 can work at a higher level ...
You're welcome.

Here's another quick example. It may or may not touch on your or the GGGP's point. So, no need to reply. I just thought I'd post something more while we wait for their reply.

    say now - INIT now
This displays the time difference between the normal run-time moment that the `now` call before the minus is called and the `now` call after the minus sign which is run during the earlier INIT phase of execution: https://docs.perl6.org/language/phasers#phasers__INIT

In P5, phased code didn't return values so one needed to create a variable and initialize it elsewhere in the code.

P6 has many more phasers than P5 and allows many of them to return values to code that's run at a different phase. (This is sometimes called "time traveling" code.) This saves a lot of jumping back and forth needed to understand a fragment of code.

>You're welcome. Here's another quick example. It may or may not touch on your or the GGGP's point. So, no need to reply.

I'll reply anyway, to say thanks again :)

I'll admit that I'm a bit phased by P6 phasers :) Had come across them just recently in the docs, initially thought, on a brief look, that at least the ENTER and LEAVE phasers were something like Python's __enter__ and __exit__ special methods used with context managers / "with" statements, or a way of wrapping a function call (or statement) in pre- and post-function invocations. which can be done with decorators in Python. But it seems like phasers may be something more, if not different. Also, there are many other kinds. Need to look into it some, including the "time traveling" code part.

> I'll admit that I'm a bit phased by P6 phasers :)

.oO ( a pun set to stun? )

> ENTER and LEAVE ... like Python's __enter__ and __exit__

Aiui their primary usage involves similar use-cases and sugar.

The common aspect is that some code is declared and then the language/compiler calls that code when the time is right.

> But it seems like phasers may be something more, if not different.

Aiui P6:

* Has more of these hooks for block processing (eg PRE and POST for convenient pre and post condition assertions);

* Has hooks that aren't for blocks but instead much larger scale phases of program execution (eg there's CHECK which runs at the end of compilation, before running the program).

Also...

> Need to look into it some, including the "time traveling" code part.

What I meant by "time traveling" is as follows.

The compiler calls phased code when the times is right. Some of the time this is implicit. For example, in this Python code the __exit__ code gets called automatically by the Python interpreter as the block is exited:

    with foo as bar:
      qux
P6 phases can return values as follows:

    say now - CHECK now
the first `now` happens during regular run time. But the second happens during CHECK time -- which is at the end of compilation, before the program starts to run. The compiler knows to run the CHECK code earlier, then store the result, then subtract that from the `now` run at normal run-time.

I don't recall who first called that time traveling and I'm not even sure on reflection whether it makes sense to call it that but that's what I meant.