views:

138

answers:

3

Is Perl considered a general purpose programming language?

Reading about it on Wikipedia

Perl has a Turing-complete grammar because parsing can be affected by run-time code executed during the compile phase.[41] Therefore, Perl cannot be parsed by a straight Lex/Yacc lexer/parser combination. Instead, the interpreter implements its own lexer, which coordinates with a modified GNU bison parser to resolve ambiguities in the language.

It is often said that "Only perl can parse Perl," meaning that only the Perl interpreter (perl) can parse the Perl language (Perl), but even this is not, in general, true. Because the Perl interpreter can simulate a Turing machine during its compile phase, it would need to decide the Halting Problem in order to complete parsing in every case. It's a long-standing result that the Halting Problem is undecidable, and therefore not even perl can always parse Perl. Perl makes the unusual choice of giving the user access to its full programming power in its own compile phase. The cost in terms of theoretical purity is high, but practical inconvenience seems to be rare.

So, it says that though Perl has the Turing complete badge, it is different from other languages because gives "the user access to its full programming power in its own compile phase". What does that mean? What programming power does Perl provide me at compiling phase that others don't?

+6  A: 

There are no features of Perl that do not appear in any other language. Lisp can do anything (Lisp is an example, here.). So perhaps we can narrow the question down to what are the features of Perl that make wide behavior swings an easy thing to do.

  • BEGIN blocks (END blocks, too.) which alter the behavior during compile. So I can write Perl code that changes the location of modules to be loaded.

    Even the following code might have a different meaning.

    use Frobnify;
    Frobnify->new->initialize;
    

    Because I could have changed where Frobnify loads from:

    BEGIN { 
        if ( [ localtime ]->[6] == 2 ) { 
            s|^/var|/var/days/tuesday| foreach @INC;
        }
    }
    

    So on Tuesdays, I load /var/days/tuesday/perl/lib/Frobnify.pm

  • Source Filters can programmatically edit the code that will perform. (CAVEAT on source filters!) (crudely and roughly equivalent to LISP macros)

  • Somewhat along with BEGIN blocks are @INC hooks. As I can modify @INC at the beginning to see change what gets loaded. I can set a subroutine at the front of the @INC array to load anything I want to load. The hook can receive a request to load Frobnify and respond to it by loading Defrobnify.pm.

  • Somewhat along with this is Symbol Manipuation. After loading Defrobnify.pm, I can do this:

    *Frobnify:: = \*Defrobnify::;
    

    Now Frobnify->new creates a Defrobnify object!

Axeman
LISP is more commonly compiled than Perl.
delnan
@delnan it depends what you mean by "compiled". Perl is compiled to bytecode.@Axeman: "LISP can do anything": what do you mean by this? Any Turing-complete language can do anything in the sense of computability, but I've yet to see a language which didn't lack a feature I liked from some other language.
Philip Potter
@delnan: Perl is *always* compiled, so that's not possible. I'm not current on LISP, my understanding was that it was interpreted.
Axeman
@Philip Potter: Yeah, theoretically C can do self-modifying code because it can rewrite instructions in memory--but there's no facility for rewriting instructions so your chances of "doing anything" in a portable way with C is greatly diminished, despite that it theoretically has that capability.
Axeman
@Philip @Axeman Yeah, it's compiled to bytecode... upon every execution (by default?). It's not compiled in the sense e.g. Java is in that you explicitly compile it once and end up with a bytecode file and run/distribute that. I don't think "implementation reads source and internally transforms it e.g. into bytecode before it starts executing" is much different from traditional interpreting.
delnan
@Axeman: thanks, that's precisely my point. Is it not possible that LISP lacks some similar feature which another language provides? For example, OO Roles such as Perl's `Moose::Role`?
Philip Potter
Add on to last point: There is no doubt that C is the implementation of most modern languages (compilers, VMs). So C is "doing it", but it's been the choice of most of the C-literate crowd to use another language to manage the complexity
Axeman
If it's not clear, I'm disputing that "LISP can do anything". I don't think LISP can do anything, but I don't know it well enough to identify a feature it lacks. Does it do OO? Does it do roles? Does it do aspect-oriented programming?
Philip Potter
I'm inclined to say that OO is structure, not function.
sreservoir
@Philip Potter: It is my understanding that LISP can do what roles accomplish, not that it can as cleanly represent the same constructs. It's a question of whether anything large enough is *clear* in LISP. (But I live in a Perl-house, so not throwing stones here.) I remember reading that the original authors of LISP never meant *it* to be the language, just a resource implementation of s-expressions. Really "LISP can do everything" is a quick statement to address the topic that nothing is completely unique to Perl. It's a sort of metonymy.
Axeman
@Axeman i agree that nothing is unique to perl -- after all, ruby stole all the best bits ;)
Philip Potter
@Philip Potter, Common LISP has CLOS which can do OO, Mixins, and aspect-oriented programming. One if LISP's key features is that it can be extended in a way that makes the extension appear to be no different from a built in construct. In C or Perl code is an unstructured string that a program would have to parse by hand to manipulate. This is often error prone. LISP on the other hand treats all code as a data structure which it will execute or pass it into special functions that can alter the structure before execution.
Ven'Tatsu
@Ven'Tatsu: thanks, that's very interesting. You're wrong about Perl lacking extensibility though: using `Devel::Declare` you can put your own hooks into the parser to create your own syntax without the problems you mention. Hence `MooseX::Declare` which introduces a declarative OO syntax to Perl.
Philip Potter
@Philip Potter, I'm aware of `Devel::Declare` and I use `MooseX::Declare` in my own code, but it's not even close to what LISP does. If you look at how `Devel::Declare` works any extension still needs to parse an string to modify syntax. It's two big advantages over prior attempts are being localized to a key word instead of having to parse a whole file, and a set of functions borrowed from perl's internal that make it easier to parse like perl parses. However my point was that in LISP the source would have already been parsed for your extension to modify.
Ven'Tatsu
+1  A: 

The simple answer is that BEGIN blocks provide Turing-completeness:

BEGIN {
    my $foo = turing_machine_simulator($program);
}

BEGIN blocks are executed as soon as the perl compiler sees them. This means that the compiler can be asked to do tasks of arbitrary complexity. Anything Perl can do, it can do during its compilation phase.

Philip Potter
+4  A: 

Subroutine prototypes are a compile time feature that is more or less exclusive to Perl. Many of Perl's builtin functions impose special types of context on their arguments (scalar, list, reference, code-block, capture). Prototypes are a way of porting some of that functionality over to user defined subroutines.

For example, Perl allows you to effectively generate new syntactic constructs with the (&) prototype. This is used in modules like Try::Tiny to add try and catch keywords to the language:

    try {
            die "foo";
    } catch {
            warn "caught error: $_"; # not $@
    };

This works because try and catch are declared as sub try (&;@) { ... }. The sub name {...} syntax is equivalent to BEGIN { *name = sub {...} } which means it has a compile time effect. In the case of try, the (&;@) prototype tells the compiler that any time it sees the identifier try, the first argument must be a bare block, and following the block is an optional list.

This is just one example of prototypes, and they are able to do many other things:

$  imposes scalar context on an argument
&  imposes code context on an argument
@  imposes list context on an argument
%  imposes list context (with an even number of elements)
*  imposes glob context on the argument 
\$ imposes scalar reference context
\@ imposes array reference context
   ... for the rest of the sigils

Due to their power (and absence in other languages) prototypes can be confusing and are best used in moderation. (like every other advanced feature of Perl).

Eric Strom
Also, the name is an issue. A C prototype is entirely different than a Perl prototype.
daotoad