Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

RFC: Simulating Ruby's "yield" and "blocks" in Perl

by LanX (Canon)
on Apr 22, 2013 at 16:52 UTC ( #1029925=perlmeditation: print w/ replies, xml ) Need Help??

UPDATE: Please note: Even if Rubyistas claim it, yield / block constructs are not coroutines

Intro

The following is a standard example in Ruby for the combination of code-blocks {... } and yield-statement:

#!/usr/bin/ruby def test puts "You are in the method" yield 1 puts "You are again back to the method" yield 2 end test {|a| puts "You are in the block #{a}"}

Output

/usr/bin/ruby -w /home/lanx/perl/ruby/yield.rb You are in the method You are in the block 1 You are again back to the method You are in the block 2
Goal
sub test { say "You are in the method"; yield 1; say "You are again back to the method"; yield 2; } test b{ say "You are in the block $_[0]" };

should produce the same results

Approach

Just to prove my strong opinion that Ruby is semantically just a Perl dialect I tried to simulate it with some syntactic sugar:

use strict; use warnings; use feature qw/say/; =pod Block sub is basically syntactic sugar for taking an anonymous sub {} and returning it. Blessing it with "Block" facilitates syntax checks. =cut sub b(&) { my $c_block=shift; bless $c_block, "Block"; return $c_block; } =pod "yield" uses a little know trick to access the @_ of the caller. It checks the last argument of the caller, if its a ref blessed to "Block" and calls it with the own arguments. Note: Ruby allows mixing normal arguments and blocks, as long as the block is in the last position. When using & prototypes in Perl like in C<test(&&)> only the first block can be passed w/o leading sub. =cut sub yield { package DB; my ($package, $filename, $line, $subroutine)=caller(1); no warnings; my $c_block = $DB::args[-1]; package main; return $c_block->(@_) if ref $c_block eq "Block"; die "$subroutine called w/o block in $filename line $line"; } =pod simulating the ruby test-code =cut sub test { say "You are in the method"; yield 1; say "You are again back to the method"; yield 2; } test b{ say "You are in the block $_[0]" };

Output

/usr/bin/perl -w /home/lanx/perl/ruby/yield.pl You are in the method You are in the block 1 You are again back to the method You are in the block 2

Comments

Of course all of this comes with a performance penalty.

But it's worth noting that Ruby itself is considered much slower than Perl5.

An additional implementation in XS or as a new feature pragma would solve performance issues in a compatible way, w/o breaking compatibility

Main differences remaining to the original code are:

  • Ruby tries to avoid curlies with do/end pairs: block/end pairs could be inserted in Perl but I'm not sure if it's worth the trouble.
  • No semicolons in Ruby, this could be (somehow) done with a source filter ... but again, worth the trouble?
  • argument signatures are lengthy in Perl, something like  my ($a,$b)=@_; takes >5 letters more than just |a,b|.

Curious for more comments...

Cheers Rolf

( addicted to the Perl Programming Language)

Comment on RFC: Simulating Ruby's "yield" and "blocks" in Perl
Select or Download Code
Re: RFC: Simulating Ruby's "yield" and "blocks" in Perl
by morgon (Deacon) on Apr 22, 2013 at 17:05 UTC
    I don't deny that it is somewhat cute, but I don't quite get the point of the exercise....

    Of course "Ruby is semantically just a Perl dialect" - just as I could argue that Perl is semantically just a Python dialect...

    Are languages like Perl, Python, Ruby, Javascript not all "essentially" the same?

    But a lot of Ruby-features are really nice (all syntactic sugar I know, but sweet nevertheless) so I would not mind to see them incorported into the Perl-language (on the interpreter-level - not via (sorry) ugly hacks like your "yield").

      > (on the interpreter-level - not via (sorry) ugly hacks like your "yield").

      Backwards compatibility is always an issue when discussing new features.

      Ugly hacks which work (even slowly) for old Perl versions improve the acceptance of new features.

      And normally the "ugliness" would be hidden in a module, much like the code of the Perl interpreter isn't judged for prettiness.

      update

      > Are languages like Perl, Python, Ruby, Javascript not all "essentially" the same?

      Nope!

      Just to make it measurable:

      The efforts needed for an experienced Perl programmer to learn Python are at least 10 times bigger than for Ruby.

      It's because Ruby copied semantics and even names for operators and idioms deep into details. Old docs for Ruby still mentioned the strong Perl heritage.

      And you might notice that a "ruby" is a gem and not a snake.

      AFAIK the semantic influence of Python is basically the exception system.

      update

      To give a concrete example, the equivalent for an anonymous sub in Perl or a {block} in Ruby is a lamda in Python. But a lamda is restricted to at most one statement, with the effect that some functional idioms in Python look like a cluttered mess of nested lambdas.

      update

      Furthermore Python doesn't have variable interpolation in strings, you must use printf or similar replacement techniques.

      Cheers Rolf

      ( addicted to the Perl Programming Language)

      Are languages like Perl, Python, Ruby, Javascript not all "essentially" the same?

      Mathematically, yes they are all the same. But that is not what we are talking here.

      When a syntax feature is being added to a language. We generally discuss how that can help users avoid writing repeated patterns of code.

        Mathematically, yes they are all the same.
        What is that supposed to mean? How do you "mathematically" compare programming languages (and yes I do know about denotational semantics)?

        The point I was trying to make is that when you abstract away inessential syntactic differences then Perl, Ruby and Python are the same - and I still subscribe to that view.

        During the history of computing several programming paradigms have evolved, so we have the imperative paradigm (the C family if you want), the functional paradigm with or without strong type-systems (Lisp, Haskell), the logical paradigm (Prolog) the OO-Paradigm (Eiffel) and of course all sorts of cross-breeds.

        So if you look at the whole range of languages from assembler to C and C++, Lisp, Haskell, Prolog and so on the ecological niche of Perl, Python and Ruby is exactly the same - a few syntactical differences notwithstanding (and speed comparisons of concrete implementations are for this discussion entirely irrelevant).

RFC: Simulating Signatures
by LanX (Canon) on Apr 22, 2013 at 17:07 UTC
    (I wanted to put this discussion into a separate post, think it will stir much controversy)

    > argument signatures are lengthy in Perl

    With the same technique like demonstrated in yield() one could simulate a syntac sugar function sig(), which would assign arguments from the upper caller  sig( my($a,$b) ). sig could easily handle a lot more like:

    • setting default values,
    • check of obligatory parameters,
    • handling named parameters,
    • checking types
    • automatic creation of API documentation.
    • allowing docstrings

    But the need to declare each variable to be lexical is cumbersome, who really wants to constantly repeat my ?

    sub test { sig my $x='default', my $y, named => my $named, OPT; ... code ... }

    so having a new keyword or syntax would facilitate things a lot

    something like

    sub test { mine $x='default', $y, named => $named, @OPT;
    ... }

    or even better

    sub test { | $x='default', $y, named => $named, @OPT |; ... }

    while I'm not sure if it's possible to use | w/o dangerous ambiguity.

    Cheers Rolf

    ( addicted to the Perl Programming Language)

      Just asking a small question here...

      How is

      sub test { sig my $x, my $y , \@opt; }

      Any better than

      sub test { my ( $x , $y ) = @_; }

      Don't get me wrong, I understand you add a few extra features to the first thing. But the user will have to learn/do extra syntax, without getting regular signature syntax like other C languages have. Besides these don't offer signatures in the true sense.

        > Besides these don't offer signatures in the true sense.

        I suppose with true signatures you mean something like test($x, $y , @opt).

        This can only be added by modifying the parser, something far beyond syntactic sugar.

        I heard that p5p recently discussed it but with no real outcome (plz prove me wrong).

        The essential problem with defining signatures is that the variables (normally) need to be lexicals.

        > I understand you add a few extra features to the first thing.

        But that's the point. I recently gave a longer workshop for my client about how to do parameter check in Perl. This really results in much code... and many people where either frustrated or bored.

        Many extra features within an easy syntax do motivate people! And maybe one day these extra features could be reused by a "true" implementation of signatures.

        Cheers Rolf

        ( addicted to the Perl Programming Language)

Re: RFC: Simulating Ruby's "yield" and "blocks" in Perl
by BrowserUk (Pope) on Apr 22, 2013 at 18:48 UTC

    Trouble is, I cannot see any use for this scary-action-at-a-distance feature that isn't more easily and clearly satisfied by passing a coderef. Can you?


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

      1. It demonstrates Perl's flexibility ¹ and the power of syntactic sugar.

        In other words: Python has no chance to mimic this! (AFAIK)

      2. Technically it's better readable than repeatedly doing stuff like

        my ($c_ref) =@_; $c_ref->(1);

        When using idioms which heavily rely on passing code-blocks it's just a matter of huffman-optimization to abstract the repeated parts away.

        I think you can't "see the use", because Perl doesn't use such idioms as often as Ruby does.

        Without the performance penalty, I'd use it straight away.

        (Keep in mind that many people ignore how slow Ruby is, for the sake of "pretty syntax")

      3. It's a good counter argument for people daemonizing Perl and adoring Ruby.

        I just read the first chapter of "7 Languages in 7 Weeks" which was a funny/shocking experience.

        For instance it praised many features like post-fix if or unless which stem from Perl³ ... just as if Ruby invented it.²

        Why not pirating some of the hype for us?

      4. It shows that an alternative parser for Perl5 could have attracted the audience that Ruby gained.

        Ruby's success is about pretty syntax on Perl semantics. Ruby's success is still heavily shocking the Pythonistas, who believe that Guido is God's (= John von Neumann's?) last prophet.

        (Moose showed how to cover the object model, covering the functional part closes the gap.)

      5. And it demonstrates possibilities to prototype future features w/o ignoring backwards compability.

      6. Last but not least, I already expected you objecting. =)

        But rather about b{ } being superfluous and expensive, while "easily" replaced by sub { } w/o performance loss.

      Cheers Rolf

      ( addicted to the Perl Programming Language)

      ¹) "I'm every woman, its all in me" - Chaka Khan =)

      ²) Not talking about the strange and inflationary use of the term DSL within the Ruby community. If there is a mainstream language w/o Lisp's reader macros, which is predestinated to define DSLs, then it must be Perl. (Ok I don't know all maistream languages ... )

      ³) And are criticized in PBP! =)

        1... 2... 3... 4... 5...

        Hm. You've chosen describe reasoning for your doing the emulation; rather then what that emulation (or the original feature) would be useful for.

        6. I already expected you objecting

        I guess that just goes to show how easy it is to see what you expect to see.

        I meant the little used and not very useful Ruby feature, rather than your overly complex emulation of it.


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.

        If you call the thing "$c_ref" then sure, it did not give you any advantage over the ill-named keyword. If on the other hand you use a descriptive name, the code gets must more readable. The Ruby syntax gives you no hint about what is the block supposed to do, what is it supposed to accept or return, it doesn't tell you anything! On one side you hear rubystas babble about how bad the Perl default variable $_ is and then they use something like this for something conceptually much more complicated and give you no chance to name the thing should you feel the need to. Perl let's you write foreach (@array) as well as foreach my $meaningful_name (@array), Ruby doesn't give you a chance to name the coderef/closure. It doesn't even admit there's one. To make things even funnier, it doesn't even tell you the method accepts a coderef (or block if you will) and the only way to find out is to read the method's body. The (&) at least tells you the subroutine expects a block, Ruby doesn't tell you anything.

        If you do not want to name the coderef, go ahead an use $_[-1]->(1), it's just as cryptic as yield.

        BTW, please translate these two to Ruby:

        sub search { my $wanted = shift; foreach my $path (@_) { print "Searching in '$path'\n"; find($wanted, $path); } } # I don't care about the File::Find ... how do you pass the block to a +nother subroutine/method?
        sub walk_tree { my ($self, $branch_handler, $leaf_handler) = @_; if ($self->isLeaf()) { $leaf_handler->($self->{data}); } else { $branch_handler->{$self->{data}}; foreach my $child ($self->children()) { $child->walk_tree($branch_handler, $leaf_handler); } } }

        Now how big is the leap from a single block to multiple in Perl and in Ruby? How hard is it to do something nontrivial in Perl and in Ruby? Ruby's syntax makes simple things short and awkward and hard things next to impossible.

        Jenda
        Enoch was right!
        Enjoy the last years of Rome.

        FWIW, the equivalent in idiomatic Python3 is:
        def test() print("You are in the method") yield 1 print("You are again back to the method") yield 2 for a in test(): print("You are in the block #{}".format(a))
        As for anonymous multiline blocks, a while back a monstrosity was released...
Re: RFC: Simulating Ruby's "yield" and "blocks" in Perl
by Limbic~Region (Chancellor) on Apr 22, 2013 at 20:56 UTC
    LanX,
    Have you read Elian's take on coroutines? I was trying to find the excerpt from Knuth on them but my google fu is weak today so this is as close as it gets. In any event, the idea of having two cooperating processes seems like a useful feature but emulating it doesn't.

    Update: Coroutine has a contrived example of how it might be beneficial.

    Cheers - L~R

      > Have you read Elian's take on coroutines?

      Thanks a lot, not yet.

      But is this Ruby feature really demonstrating coroutines?

      We don't have multiple entry points and there's no reentering at the last suspended exit point.

      Are you maybe confusing Python's yield with Ruby's yield?

      > I was trying to find the excerpt from Knuth

      I really should start reading Knuth ...

      FWIW I demonstrated at YAPC:EU 2011 how to emulate Perl6's gather/take in Perl5.

      > the idea of having two cooperating processes seems like a useful feature but emulating it doesn't.

      Well, I think a convincing prototype which fits well into the language is the first step for implementation.

      And backwards compability is always an issue, most module authors wouldn't chose a feature which is only available in the very newest release.

      One of our perlmongers just told us at the last meeting that at work he's stuck with 5.8.6 on AIX!

      Cheers Rolf

      ( addicted to the Perl Programming Language)

        LanX,
        One of our perlmongers just told us at the last meeting that at work he's stuck with 5.8.6 on AIX!

        I am stuck with 5.8.8 on AIX with no compiler and no direct internet connection. That means no C extensions and only stand alone pure perl modules (or at least ones with small dependency trees).

        Cheers - L~R

Re: RFC: Simulating Ruby's "yield" and "blocks" in Perl
by runrig (Abbot) on Apr 22, 2013 at 23:20 UTC

    Update: Didn't realize that Ruby's 'yield' did not involve 'real' coroutines. The OP has since been updated.

    Here's my take on accomplishing "sort of" the same thing in a couple of different ways (w/regard to argument passing) w/Coro. I know it's not the same thing, but worth comparing and contrasting and thinking about:

    use Coro::State; use Coro::Channel; { my $new; my $test = sub { my ($m, $f) = @_; print "In method\n"; $f->(1); $new->transfer($m); print "In method again\n"; $f->(2); $new->cancel(); $new->transfer($m); }; my $main = Coro::State->new(); my $f = sub { print "In block: $_[0]\n" }; $new = Coro::State->new($test, $main, $f); while ( !$new->is_zombie() ) { $main->transfer($new); } print "Done!\n"; } { my $new; my $test = sub { my ($m, $q) = @_; print "In method\n"; $q->put(1); $new->transfer($m); print "In method again\n"; $q->put(2); $new->cancel(); $new->transfer($m); }; my $main = Coro::State->new(); my $ch = Coro::Channel->new(); $new = Coro::State->new($test, $main, $ch); while ( !$new->is_zombie() ) { $main->transfer($new); my $got = $ch->get(); print "Inblock: $got\n"; } print "Done!\n";
      Thanks, looking into coro is on my todo list for .... ehm ... well ... some time now! =)

      But do you agree that this is far from having any syntactic sugar? (rather "syntactic vinegar" ;-)

      Just to avoid misunderstandings, Ruby's yield doesn't help creating coroutines!

      The functions are executed in one run till the end and the yield are just executions of callbacks given by blocks.

      Python OTOH uses yield a statement in so called "generators" to define iterators which freeze their state each time they return with yield.

      Much like gather and take in Perl6.

      Cheers Rolf

      ( addicted to the Perl Programming Language)

Re: RFC: Simulating Ruby's "yield" and "blocks" in Perl
by Jenda (Abbot) on Apr 23, 2013 at 01:03 UTC

    For gawds sake why would I want to do anything that silly, replacing something readable with the use of an ill-designed and ill-named nonsense? Yieiaild? There's nothing in the dictionary that would match the way this keyword is used in Ruby. Yeah, yield as a keyword related to coroutines, fine. You yield the execution and let someone outside the subroutine decide when to continue, but that's not what it means in Ruby!

    sub test (&){ my $print = shift; say "You are in the method"; $print->(1); say "You are again back to the method"; $print->(2); } test { say "You are in the block $_[0]" };

    is not only more readable, but also more extensible! You are not restricted to a single "callback" and if you need to pass the subroutine reference/closure/block (I know the differences, thank you very much) somewhere else you can! Try that in Ruby!

    BTW, does the |a,b| finally declare new variables in Ruby or does it still reuse the a and b if they happen to be defined? Yeah, I know, a method in Ruby should not take more than four lines ... it doesn't matter you end up having hundreds of them.

    Jenda
    Enoch was right!
    Enjoy the last years of Rome.

      You seem to be somehow ... tensed ...

      ... everything alright with you?

      Cheers Rolf

      ( addicted to the Perl Programming Language)

        Mostly just bad memories. I was forced to work with Ruby a few years ago. The language is ugly.

        Jenda
        Enoch was right!
        Enjoy the last years of Rome.

Re: RFC: Simulating Ruby's "yield" and "blocks" in Perl
by sundialsvc4 (Monsignor) on Apr 23, 2013 at 11:52 UTC

    I find co-routines useful, especially as filters or as generators for use in a loop.   But their internal implementations can be ugly.   While it would be nice to have them in this language, “there’s more than one way to do it.™”   As BrowserUK says (in so many words), I would not disabuse the Perl-5 language in order to do it in just this way.

    For example, there are already closures in Perl ... the “coderefs” that were mentioned ... and these can be used to implement finite-state machines (FSMs) which will return a particular value each time they are called.   Presto, you have accomplished the intended effect, using the ordinary facilities that this programming language already offers you.   No, it’s not exactly the same thing, but a well-written implementation is clear.   Bloom where you are planted.

    Anyhow, there are other threads on this, like Coroutines in Perl from back in 2004 ... and I can’t help but notice that, even “way back then,” the Perl-6 / Parrot people were telling us all how good it was going to be.   Someday.   ;-)

      Sorry I'm tired of repeating it, but even if Rubyistas claim it, yield / blocks are not coroutines, they are syntactic sugar for callbacks.

      And I think Matz is to blame for choosing the misleading term "yield" in this context.

      (Like Larry is to blame for some misleading "List" <-> "Array" terminology)

      And BTW Perl5 has coroutines, passing parameters is just ugly to implement and I'm not sure about the performance gain.

      Go take a look at the goto &sub syntax.

      Cheers Rolf

      ( addicted to the Perl Programming Language)

      Okay, take your word for it.   Just meant it as a colloquial, informal usage term.

      “Rubyista” ... that’s a fun mental-image to play with.   :-)

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://1029925]
Approved by Corion
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others imbibing at the Monastery: (11)
As of 2014-07-23 12:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite superfluous repetitious redundant duplicative phrase is:









    Results (141 votes), past polls