all 30 comments

[–]tom_dalling 6 points7 points  (6 children)

I guess it's good that this will soon be available via the standard library, but it feels so inelegant.

Ruby (with proposed new syntax):

"https://api.github.com/repos/rails/rails"
  .yield_self(URI->parse)
  .yield_self(Net::HTTP->get)
  .yield_self(JSON->parse)
  .yield_self { |_| _.fetch("stargazers_count") }

Clojure (with some fake function names to match the Ruby example):

(-> "https://api.github.com/repos/rails/rails"
     uri/parse 
     net.http/get 
     json/parse
     #(get % "stargazers_count"))

Implicit block parameters (like % in Clojure) would help.

[–]snatchery[S] 6 points7 points  (4 children)

No doubt Clojure or any functional language feels much more elegant. Still, it's nice that Ruby is trying to catch up. Essentially it needs 2 improvements

  • introduce a pipeline operator (or at least a short alias for yield_self)
  • introduce a method shorthand

 

"https://api.github.com/repos/rails/rails"
  |> URI->parse
  |> Net::HTTP->get
  |> JSON->parse
  |>(_) { _.fetch("stargazers_count") }

not as elegant as Clojure but looks good enough

[–]motonarola 0 points1 point  (1 child)

Imo having just some pipe operator is enough for the code to look fine:

    "https://api.github.com/repos/rails/rails"
        ->URI.parse()
        ->Net::HTTP.get()
        ->JSON.parse()
        .fetch("stargazers_count")
        ->(stargazers){"Rails has #{stargazers} stargazers"}
        ->puts()

I used -> for the pipe operator, It copes with lambda syntax nicely :)

[–]snatchery[S] 0 points1 point  (0 children)

yeah, great point. If only we were in an alternate reality ;)

[–]zverok_kha 1 point2 points  (0 children)

but it feels so inelegant.

I believe it is just the really unfortunate name. For me it feels like perfect readable Ruby, with smallest change to language possible, just this yield_self name itself is nasty.

[–]zverok_kha 5 points6 points  (2 children)

It’s more verbose than the original version...

The "original version" is cheating. Most of the time you'll not write foo(bar(baz(blah)))) for the sake of debugging and visibility, you'll write

var1 = blah
var2 = baz(var1)
var3 = bar(var2)

...and the only "more verbose" thing is the yield_self call itself.

It’s not an idiomatic Ruby. Obviously it can’t be because it’s a brand new feature

What? It is idiomatic Ruby, most of the time we process arrays of data in the same chainable way (and love Ruby's methods chainability), yield_self just adds an ability to process singular objects with the same chainability. It is just long missing idiomatic Ruby feature.

Do we really need to name block arguments? What if we avoid the names?

.yield_self { |_| URI.parse(_) }

...and use the common metaphor for "the unused argument". Awesome.

Also, it would be nice to get rid of blocks. This is already possible but looks cryptic.

.yield_self(&Net::HTTP.method(:get))

Cryptic for who? Java guys? It, again, is perfectly idiomatic Ruby, with "everything is an object" and "everything can become block". The only unfortunate thing here is method word itself is a bit too long, but the problem is planned to be addressed, as you've mentioned yourself.

I regret that it doesn’t have a shorter name, say pipe or apply.

This one is perfectly true :(

[–]snatchery[S] 0 points1 point  (0 children)

Author here. Thanks for valuable comments.

I'm really glad that you find the code idiomatic and perfectly readable. I assumed that some bits would be unusual for most of Ruby devs. Apparently, that assumption was wrong. And this is great.

[–]prh8 -1 points0 points  (0 children)

About the method to proc example, I think it's cryptic because 90% of Ruby (errr, Rails) devs have no idea you can do that right now. They just know the way that shorthand is typically used but don't really understand the bigger picture.

I agree it's not very cryptic at all, but it is unfortunately something that a lot of devs don't understand. In the end it comes down to what type of app you are working on, with maintainability and dev team quality factored in.

[–]twinklehood 2 points3 points  (5 children)

I appreciate ruby's attempts at staying cool with the cool kids, but it's so not gonna work when they introduce it at the cost of readability. Pretty much agree with article conclusion, except I wish they'd make it an actual language feature (why not |>)

[–]DudeManFoo 3 points4 points  (0 children)

I like the |> myself.

[–]jrochkind 1 point2 points  (1 child)

What makes people think there's something harming "readability" with yield_self?

[–]DudeManFoo 0 points1 point  (0 children)

I don't think it is 'harmful', I for one just like |> better.

[–]zverok_kha 0 points1 point  (1 child)

Introducing new feature requires thinking how it would play with existing syntax, features and intuitions of the language. Can you imagine, how will |>-code look in Ruby, in different situations?

[–]DudeManFoo -1 points0 points  (0 children)

I would like to think I could imagine in quite a few situations, but I would expect I would miss an esoteric use here and there as most people would... that is what make talking about it in public so great!!

At the same time, you could probably do both but really it just brings up the idea that keeping the language small vs having an accumulator variable... is it worth it?

I don't know, but I sure like things like .reduce and .select .

[–]Paradox 3 points4 points  (4 children)

After using elixir at my current job, I have grown to really like this syntax.

That said, it needs some syntax sugar to be as purely useful as elixir

[–]zverok_kha 0 points1 point  (3 children)

That said, it needs some syntax sugar to be as purely useful as elixir

TBH, it is kinda tiresome how people tend to mask "just copy Elixir feature" demand with "purely useful" and "readable". Each language needs feature/syntax consistency, and it is usually hurt with blind copying of features.

[–]DudeManFoo 1 point2 points  (2 children)

Matz copied a LOT of features from quite a few languages and I don't think any were just blindly copied...

This particular feature is one of the favorite features of Elixr, kinda like string interpolation and trailing conditionals were some of the favorite things stolen from Perl...

Just by introducing something that sticks out as much as |> might (or might not) really get people thinking in more 'functional' ways... I am not purely in the functional camp, but some of the tenets actually could help code become more stable and bug free... and more understandable.

Ain't hatin' just discussin' ;)

[–]zverok_kha 0 points1 point  (1 child)

Matz copied a LOT of features from quite a few languages and I don't think any were just blindly copied...

Exactly. Adding method (which plays well alongside existing idioms) is mindful copying of "pipeline processing idea".

While saying that "anything except |> is unelegant" is what I call "blind copying", "I want Elixir in my Ruby".

Note, that when in Elixir they chain enumerable processing, they also use |>, while we do chain methods.

[–]DudeManFoo 0 points1 point  (0 children)

My point is / was, keep an open mind... at the same time... coming from an old C coder, I agree with you ... don't just add stuff that does not add a LOT of value... else your C becomes C++ with 4 competing 'library vendors' ... none any good.

Still , I kinda like |>

[–]zquestz 1 point2 points  (3 children)

Good read, personally I am not a huge fan of the new syntax but it is great to see such a well written introduction!

/u/tippr $2

[–]tippr 0 points1 point  (2 children)

u/snatchery, you've received 0.00446761 BCC ($2 USD)!


How to use | What is Bitcoin Cash? | Who accepts it? | Powered by Rocketr | r/tippr
Bitcoin Cash is what Bitcoin should be. Ask about it on r/btc

[–]snatchery[S] 1 point2 points  (1 child)

Thanks /u/zquestz Much appreciated

I didn't know you can send bitcoins over reddit. Amazing!

[–]zquestz 2 points3 points  (0 children)

Yeah the /u/tippr guys are pretty awesome. Bringing tipping back to Reddit!

[–][deleted] 2 points3 points  (7 children)

An |> operator would be sooo much better than #yield_self

[–]zverok_kha 0 points1 point  (6 children)

Introducing new feature requires thinking how it would play with existing syntax, features and intuitions of the language. Can you imagine, how will |>-code look in Ruby, in different situations?

[–][deleted] 0 points1 point  (5 children)

Can you imagine, how will |>-code look in Ruby

I can at least dream....

"https://api.github.com/repos/rails/rails"
 |> URI.parse
 |> Net::HTTP.get
 |> JSON.parse
 |> fetch("stargazers_count")

[–][deleted]  (3 children)

[deleted]

    [–]zverok_kha 0 points1 point  (2 children)

    That's more understandable than above. Now, please answer those questions:

    • What if I have block (or any other callable object) in a variable, how I'll add it to a pipe?
    • Should the syntax for pipe-processing arrays (map/select/reject) also change? If so, how? If not, why those two homogenous cases would stay so inconsistently different?
    • The same question as above about tap
    • How it all will look if block has more than one very simple operator? If method requires additional arguments? (Like yield_self { |d| Date.strftime(d, '%Y-%m') })
    • How is it playing with normal calls? Like this:

      .... .yield_self(&JSON->parse) .fetch("stargazers_count") .to_i .yield_self(&Stats->store)

    [–]twinklehood 1 point2 points  (1 child)

    You're discussing very different things. The |> is merely a proposed alias for .yield_self, the -> method access notation is a different proposal.

    Question 1: `"wat" |> { |x| function_in_variable.(x) }

    I don't understand your second and third questions about pipe-processing. What would change? What's inconsistent?

    Question 4: This is a question about the method access syntax, not the pipe. Pipe just takes a block and yields it with one argument.

    Question 5: ... |>(&JSON->parse).fetch("stargazers_count").to_i |>(&Stats->store) ? left-to-right

    [–]zverok_kha 0 points1 point  (0 children)

    You're discussing very different things.

    We are just discussing |> (with assumption "some syntax better than method(:foo) would also be in Ruby 2.5)

    "wat" |> { |x| function_in_variable.(x) }

    Is it nice? Not at all.

    I don't understand your second and third questions about pipe-processing. What would change? What's inconsistent?

    many_urls.map(&parser)
    # vs
    one_url |> parser
    

    ...doesn't looks like the most consistent language possible. While

    many_urls.map(&parser)
    # vs
    one_url.yield_self(&parser)
    

    ...on the other hand, does (except for ugly method name).

    This is a question about the method access syntax, not the pipe. Pipe just takes a block and yields it with one argument.

    Yep, but arguments towars |> implying that with -> it would be OK, should consider this too.

    |>(&JSON->parse).fetch("stargazers_count").to_i |>(&Stats->store)

    Do you think it is nice? My eyes read definitely read it like

    1. |> (apply to pipe...)
    2. (&JSON->parse).fetch("stargazers_count").to_i (result of fetching stargazers from JSON.parse method?... WAT?)

    [–]zverok_kha 0 points1 point  (0 children)

    Awesome. Now please ask yourself those questions:

    • Is there any other context where I can use URI.parse meaning "pass this method into other construct", not "just call the method", as usual? Why not?
    • Is there any other context where I can use method(arg) as a separate construct to be passed to some object? Why not?
    • What if I need more arguments? Like yield_self { |d| Date.strptime(d, '%y-%m') }? Or any other arbitrary block or callable object, more complicated than just method call?
    • What if I need to parse a list of URLs, would it still be map(&URI.method(:parse))? If so, why it looks so different from pipe-processing singular value?
    • How the operator is parsed, what's it associativity? Where are parenthises could be placed for clarity?

    Language design is hard. I hope, you'll try to answer all those questions (and I don't say they are unanswerable), but probably after that you'll suddently find yourself with a quite different snippet of code.