Hacker News new | past | comments | ask | show | jobs | submit login
Drat – Ruby has a double splat (firmafon.dk)
93 points by kaspth on June 23, 2015 | hide | past | favorite | 36 comments



This is probably quite an informative and interesting article (though I found some of the examples troublesome to understand and I doubt they'd make it into any codebase I had anything to do with)... but the twee tone made it agonising to read!


This applies to a large swath of the Ruby community for me.


This seems to be pretty identical in behaviour to what you can do with the args and kwargs arguments to functions in Python and how you can expand a list or dict when passed in as an argument. The one thing I haven't seen before is how you can name the yielded arguments.


> options = { a: 'b' } > { c: 'd', options } # => { :c => "d", :a => "b" }

> I don’t know when this is useful.

Ah! When I was learning Ruby after being a long time perl user, this was the one huge missing feature that I longed for. Perl splats automatically (you have to use refs if you don't want it to), and that leads to nice option construction:

    @opts = ($some_flag1 ? (option1 => $some_flag1)      : (),
             $some_flag2 ? (option2 => { a => 1 })       : (),
             $some_flag3 ? (option3 => [ 1,2,3 ])        : (),
             $some_flag4 ? (option4 => [$some_flag4, 1]) : ())
But it was annoying in Ruby. The best way I found previously was with a bunch of chained .merges, one for each option:

    opts = {}.merge(some_flag1 ? {:option1 => $some_flag1}      : {})
             .merge(some_flag2 ? {:option2 => { :a => 1 }}      : {})
             .merge(some_flag3 ? {:option3 => [ 1,2,3 ]}        : {})
             .merge(some_flag4 ? {:option4 => [$some_flag4, 1]} : {})
Ugly and verbose compared to the perl (which is usually not the case in perl vs ruby). This new syntax allows for:

    opts = {**(some_flag1 ? {:option1 => $some_flag1}      : {}),
            **(some_flag2 ? {:option2 => { :a => 1 }}      : {}),
            **(some_flag3 ? {:option3 => [ 1,2,3 ]}        : {}),
            **(some_flag4 ? {:option4 => [$some_flag4, 1]} : {})}
Which looks only marginally better, but allows nice things like interleaving optional and non-optional arguments:

    opts = {**(some_flag1 ? {:option1 => $some_flag1}      : {}),
            :option1_related => "xyz",
            **(some_flag2 ? {:option2 => { :a => 1 }}      : {}),
            **(some_flag3 ? {:option3 => [ 1,2,3 ]}        : {}),
            :option4_related1 => "xyz",
            :option4_related2 => "pdq",
            :option4_related3 => "abc",
            :option4_related4 => "123",
            **(some_flag4 ? {:option4 => [$some_flag4, 1]} : {})}
That's quite nice.


This is also now in Python 3, both for single and double splats.


I love programming, if only for its vocabulary. Where else can you use bangs, splats, sloshes, whacks and thunks on a daily basis? Apart from the comic book industry that is.


My favorite is the 'empty promise' from ES6:

  new Promise((resolve, reject) => {
    ...
    resolve();
  });


If you're just resolving, use this [1]:

  Promise.resolve();
[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


I'm really not a fan of the keyword argument feature in Ruby, especially after using ES6 for a good number of projects. It's essentially a special-case of variable destructuring that works for one type of key, Symbols. Also, since its coupled into the arguments of a function, there is no way to use this sort of feature for simple variable assignment.


I agree. Also, consider how much more democratic and thoughtful the process that ES6 underwent to add destructuring was, compared to the way Ruby evolves... many of us got to use the feature via transpilation long before the standard was baked, which led to a much more solid feature.


Yeah, it's a real testament to the ES community. I'm amazed how much has been accomplished recently in such a transparent and collaborative way. Hopefully that sort of thing is more the "new normal" than an exception to the rule.


It seems rather fitting that the Web's language would be developed in a transparent and collaborative manner.


Is this very much different from how arguments work in Scheme or Lisp? Seems like this might just be a feature of interpreted languages.


It's been said that many dynamic languages have taken deep hints from lisp, so no surprise it's pseud-metalevel call protocol (apply fun <list>) is close to ruby and python ones. Although I've never used (apply ...) with keyword arguments.


I am reminded of something in Brian Carper's old post "Keyword Arguments: Ruby, Clojure, Common Lisp":

http://briancarper.net/blog/579/keyword-arguments-ruby-cloju...

This reminds me of everything I love, but also everything I hate, about Ruby. He writes:

------

With even more added sugar, you can leave off the parens in Ruby function calls. So this is pretty common in Ruby:

foo :x => 123 # => {:x=>123}

How nice and punctuation-less. But then things get ugly. What about this?

foo {:x => 123}

That won't even compile.


Hah, I had to try this out to believe it.

You can do `foo d: {:y => 2}` or `foo :d => {:y => 2}` but not a raw hash literal without parens.

That's parse.y[1] for you...

[1]: https://github.com/ruby/ruby/blob/trunk/parse.y


It's because Ruby thinks {} is a block. All Ruby methods can implicitly take a block (why? who knows...), so the single-line block syntax is parsed as such before it can be considered a hash argument into the method.


Although splat and option hashes (double splat before double splat came along) are something I use everyday, I almost invariably end up thinking they increase (cyclomatic) complexity. Quite often, having a more strongly typed class as a method parameter instead of a hash is actually what I want. Double splat and the like sometimes make it easier to do the wrong thing. Worse still, they seem to promote it.


Triple splat, no erasies, touch blue make it true.


, final destination.


Am I wrong to think that this new Ruby feature is just an inferior, more confusing version of what is found in Python since forever?


Hey you have the right to downvote but an explanation would be welcome.


It seems sometimes like the only thing that has changed significantly in Ruby since I started with 1.8.4, ten years ago, is that it now has a real bytecode and better garbage collection.

So sad to see a language stagnate like this.


Speaking as someone who isn't much of a Ruby user, what do you expect to see? I mean, when you have Smalltalk-style messaging and metaprogramming, that's a relatively timeless and composable base.


What, precisely, are you looking for? The stdlib continues to grow and improve, MRI continues to get faster, we just got full native unicode support a while back, GMP usage in Bignum, refinements, symbol GC, compiling with jemalloc... and that's just stuff in 2.1 and 2.2.


Well, that's not true. Ruby's changed massively since the 1.8 series. What are you expecting to see that you're not?


I think "massively" is relative here. If you compare Ruby 1.8 and Ruby 2.2, they are very clearly the same language, but the latter with many pleasant and useful additions - the sort of iterative improvements, in fact, that Apple are so good at with their hardware (for the most part). If you compare JavaScript from the 1.6 era to today's ES6... they hardly look like the same language.


> Ruby's changed massively since the 1.8 series

It was mostly syntactical, and it didn't do much to improve readability.

I guess I was hoping to see functional improvements, where they address the pain points of ruby: performance, tooling, dependency hell, unicode support, lack of things like co-routines or generators, the ability to restrict access to internals.


The first "pain point" you mention is "performance" but earlier you were saying that the only major changes were a better GC and JIT? I don't see how that follows.

What's odd is that "performance, tooling, dependency hell, unicode support, ... co-routines ... generators" reads like a list of things that have been worked on heavily since 1.8. Performance has been a major focus of every release since 1.9.1; integrating Rubygems considerably helped with tooling and dependency issues; encoding-aware strings were a major feature of 1.9; coroutines and generators can be implemented more effectively with 1.9's Fibers.


Ok, well I guess I was underwhelmed by the activity I did see:

* If I want to increase performance, my option is basically a c module. Except, this doesn't have e.g. the Cython tooling the python community has. * Tooling is miserable. There are few ways to restrict the way people abuse the language, and as a result, style is often highly inconsistent and subjectively-based. Say you want to find all the places people have parse CSVs in an ad-hoc manner: you're pretty much screwed because there are a dozen ways to do it, so a code search only likely to return a subset of your intended code. This may sound like a critique of dynamic languages, but I have not experienced this nearly as much with either scheme or python. * unicode support was still sub-optimal; I personally don't like mixing string/locale operations with encoding operations. I haven't played with this in a while, however, so it may have changed. * Integrating Rubygems was the dependency hell I was talking about. It's as bad as NPM—dependency bounds are often impossible to resolve because there have been breaking changes since the gem last updated. Warnings or conflicts are often ignored until someone offers a pull request, and sometimes (the nightmare scenario) they only appear during runtime. Vendor repositories (or a list of pinned versions) become critical, and security patches can sometimes can all-nighter patches to third-party modules you desperately depend on. This may speak more to the community, but I would have expected better ability to at least provide a (semantic) guarantee a minor point change would not break the api interface. This might not be possible with a dynamic language, but even trivial tooling would help here. * co-routines and generators do exist now, my apologies. The syntax is, again, awkward, but this is a subjective note that I've learned is not universal.

I guess I'm just surprised ruby hasn't doubled down on making the platform attractive to agile enterprise teams, and right now it seems like a money sink compared to e.g. scala/go/python, all of which offer comparable productivity (IMHO) with greatly increased guarantees for maintainability, performance, profiling/tooling/backend options. It makes the prospect of having a technology pivot of some sort nauseous.


> guess I was hoping to see functional improvements, where they address the pain points of ruby: performance, tooling, dependency hell, unicode support, lack of things like co-routines or generators

Performance and Unicode other people have mentioned alreaedy, and have been areas of improvement. Explicit library support for semi-coroutines and coroutines was added in 1.9 via Fiber and Fiber::Core, and Ruby already had generator support in 1.8.

So many of the things you say you were hoping to see have been addressed since 1.8, and at least one was already addressed in 1.8.


> tooling, dependency hell

I used Rails when it was still `config.gem` with no dependency resolution, and it was an absolute nightmare, so I think I know what you're talking about here. But then bundler came along and, well, completely solved the problem.


Re: Generators - I don't know why it's so hidden, but the Enumerator class allows you to make generators

```

enum = Enumerator.new{|y| a = 0; loop{y << a; a += 1}}

enum.next => 0

enum.next => 1

etc

```

I believe this has existed since 1.8x


What makes you think they're hidden? Enumerator moved from stdlib to core in 1.9 and also calling #to_enum isn't needed anymore:

    enum = 0.upto(Float::INFINITY)
    enum.next # => 0
    enum.next # => 1


It's not "hidden" per se, it's the first SO result for googling "ruby generators".


What exactly would you like to see?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: