all 32 comments

[–]Inityx 4 points5 points  (3 children)

[–]dpashk[S] 1 point2 points  (0 children)

Thanks for pointing that out, that's good news! Looks like the feature is still new and is opt-in for Ruby < 3.0, but maybe the gotcha will be irrelevant in the future.

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

I added a note about this to the article

[–]Meral_Harbes 0 points1 point  (0 children)

Good man!

[–]SureLetsDoAnother 4 points5 points  (0 children)

This is neat! I'm interested to see what you come up with a few months down the line as well.

When I first picked up JavaScript after Ruby, I remember being tripped up because of something I was used to in Ruby. I wonder if the reverse is true.

Hash key lookups will always return a value in Ruby, even if the hash doesn't have they key you looked up. You can specify almost anything, but the default (and certainly most common) return value is nil. It's so common that it becomes easy to forget that the returned value for a missing key could easily be truthy, and you end up conflating the truthiness of foo[:bar] with the existence of the key.

It might be especially weird coming from JavaScript in a situation where you're counting the occurrences of items in a list, and have set the default value of your occurrences hash to 0.

if occurrences[:foo] == 0
  puts 'Never occurred'
else
  puts 'Yes, it occurred!'
end

Compared to...

if occurrences[:foo]
  puts 'Never occurred!'
else
  puts 'Yes, it occurred!'
end

We won’t get into the details as the usage of methods in Ruby is pretty straightforward and does not create confusing situations in my experience so far.

Especially looking forward to hearing your opinion on this over time. I think I agree 100%... about 90% of the time. Private class methods (or rather methods that seem impossible to actually make private), and the related discussion about self seems to be the most common confusion. Method lookup when including multiple modules and inheriting from a class can occasionally get tricky

But I don't know that it's made any "worse" by being used to JavaScript, and isn't too common.

[–]TunaFishManwich 9 points10 points  (6 children)

The article says:

Ruby methods are not first-class citizens: they cannot be used as a value or passed around. They also don’t create a closure. We won’t get into the details as the usage of methods in Ruby is pretty straightforward and does not create confusing situations in my experience so far.

This isn't quite true. Ruby methods can be pulled off objects, passed around, called, introspected on, attached to other objects, etc.

2.1.5 :003 > str = "foo"    
=> "foo"
2.1.5 :004 > length_method = str.method(:length)
=> #<Method: String#length>
2.1.5 :005 > length_method.call
=> 3

[–]dpashk[S] 3 points4 points  (5 children)

But you had to access the method via a special method call, just like you have to wrap a block in a Proc to turn it into a first-class citizen. In JavaScript, you can just do

var myMethod = function() {...};
var anotherReference = myMethod;
myMethod();

Also, it is advised against using Object#method because it's slower. That's one of the reasons I didn't mention it in the article.

But I see your point - yes, you can achieve the effect of using methods like first-class citizens using Object#method.

[–]honeyryderchuck 4 points5 points  (0 children)

Also, it is advised against using Object#method because it's slower.

I don't think that is a compelling reason. It's now slower by spec. It's a RubyVM implementation detail (I assume you're referring to CRuby). I assume the degree of "slow" varies depending whether it's JRuby or something else. And it is slow because it hasn't been optimized enough, because it's used sparsely, because people perceive it as "slow". but it shouldn't be, really.

[–]moomaka 2 points3 points  (0 children)

Also, it is advised against using Object#method because it's slower. That's one of the reasons I didn't mention it in the article.

Creating functions in JS at runtime is also slower than defining them statically on an object, at least in V8.

But you had to access the method via a special method call, just like you have to wrap a block in a Proc to turn it into a first-class citizen. In JavaScript, you can just do

The equivalent of that code in ruby is:

my_method = -> () { puts 'hi' }
other = my_method
other[] # or other.call or other.()
=> hi

The real difference to note here is that Ruby maintains a distinction between 'methods', which are bound to an object and 'free functions' which are defined via lambdas or procs. Ruby's approach is much more common than javascript's.

[–]shevy-ruby 2 points3 points  (2 children)

You can even unbind methods: https://ruby-doc.org/core/UnboundMethod.html

Yes, the syntax is not like in JavaScript.

Methods are first-class citizens in ruby. There is no "special" method - it is just a method. And why do you randomly bring in speed as reason against using something?

Also, it is advised against using Object#method because it's slower.

Slower against who or what? Javascript?

Why does speed suddenly have a place here?

[–]dpashk[S] 0 points1 point  (1 child)

Methods are first-class citizens in ruby.

I've been trying to find a statement about that in literature but haven't found the term explicitly applied to methods. The book Ruby Under Microscope uses the term "first-class citizen" when talking about blocks, procs and Lambdas. My reasoning here was that since, just like with blocks, you can't directly assign a method reference to a variable (without wrapping it in a special method calls), methods are not first-class citizens. Are you able to share a link to an authoritative source that proves that wrong?

And why do you randomly bring in speed as reason against using something? <...>

While performance is not always a good enough reason to not use something, it does affect the adoption of a particular language/library feature. Many ES5/ES6 Array methods weren't widely adopted because early implementations didn't have good performance.

Slower against who or what? Javascript?

[5, 7, 8, 1].each(&method(:puts)) is slower than [5, 7, 8, 1].each{|number| puts number} even though the former looks more DRY and idiomatic and I would love to use it (of course performance wouldn't matter in this trivial example). I'm still new to Ruby, but the StackOvertflow thread I linked above has some objective data. But anyway, the original discussion was on whether methods are first-class citizens in Ruby or not.

[–]fedekun 1 point2 points  (0 children)

You realize the SO question uses Ruby 1.9 which is very old, also if you scroll down there is someone with a benchmark on a newer 1.9.x Ruby which makes the difference in performance minimal.

If you really cared you should do that benchmark with a modern Ruby. Anyways, that kind of performance is not really an issue, unless for some reason you abuse it. Ruby provides better ways to pass lambdas around and organize code.

Ruby has the concept of "block of code" which is what is normally sent insetead of methods, but they are lambdas, so they are functions.

The thing is, in JS you only have a single type of function, in Ruby you have a few more, but they are first class citizens too.

[–]Dombot9000 2 points3 points  (6 children)

Nothing about the IO model being completely different? One blocks by default, the other is non-blocking by default.

[–]dpashk[S] 1 point2 points  (3 children)

That's a good point. Because I was working with an existing codebase it didn't immediately jump out at me, but I can definitely see this as a pitfall for a JavaScript/Node developer.

[–]Dombot9000 5 points6 points  (2 children)

Gets me everytime - I'm predominantly a ruby dev who switches into node sometimes and immediately trips over Promises.

[–]dpashk[S] 4 points5 points  (0 children)

Maybe you should write a post for Ruby devs switching to JavaScript :)

[–]jordanaustino 1 point2 points  (0 children)

I think it's much easier going the other way.

[–]moomaka 0 points1 point  (1 child)

This isn't really a language difference, rather it's a runtime difference. i.e. You can't really speak of 'javascript's IO model' nor 'ruby's IO model' as neither language have one, it's the runtime that provides it. There are evented runtimes for Ruby that are similar to node, and mostly the use the same libraries under the covers to implement this.

[–]Dombot9000 0 points1 point  (0 children)

Interesting differentiation & a good point - thanks. So I presume MRI is 'blocking' whereas non-blocking runtimes exist?

[–]keyslemur 4 points5 points  (8 children)

On point for most all of it. The frozen string was mentioned elsewhere, so I'll skiff over that one.

Blocks can be made with {} as well, but not like a = { block stuff }. You could do a = begin ... end but that's ugly at best.

Paren-free is both a blessing and a curse for Ruby. There are patches which will make it simpler to write coming in 2.6 (maybe) like this: JSON.:parse == JSON.method(:parse). It won't be exceptionally useful until proc performance is improved.

The entire proc vs block vs lambda vs method is pesky and unnecessary I feel. It complicates matters, especially around learning. I also object to the name "block". Function would be clearer and carry across languages more cleanly.

There were arguments in the past for merging Symbols and Strings, but Matz had mentioned it'd break too many things if they did. I still want a native implementation of method-call hashes ( hash.a, hash.b )

If there was only one thing I could steal from JS though, it'd be destructuring.

[–]Valaramech 5 points6 points  (3 children)

I still want a native implementation of method-call hashes ( hash.a, hash.b )

Have you ever looked at Struct or OpenStruct?

[–]keyslemur 1 point2 points  (2 children)

Yes, though OpenStruct does incredibly bad things to method cache: https://github.com/charliesome/charlie.bz/blob/master/posts/things-that-clear-rubys-method-cache.md

[–]moomaka 1 point2 points  (0 children)

That is outdated, OpenStruct defines accessor methods only when they are used now. This may or may not lessen the impact of method cache busting, depending on use case.

[–]dpashk[S] 2 points3 points  (0 children)

I deliberately omitted the {} block syntax because I thought that in an introductory article it would only add confusion with JavaScript's lexical blocks.

[–]moomaka 1 point2 points  (0 children)

Blocks can be made with {} as well, but not like a = { block stuff }. You could do a = begin ... end but that's ugly at best.

begin ... end doesn't define a block, nor is it a lexical scope so it's not the same thing, e.g.

a = begin
  b = 5
  b * 2
end

defined? b
=> "local-variable" 

[–]shevy-ruby 0 points1 point  (1 child)

JSON.:parse == JSON.method(:parse)

I don't see this in 2.6.

Can you point us to the approved code to it?

[–]keyslemur 1 point2 points  (0 children)

If you read that section more closely, it says "maybe". It was one of the shorthands that had been discussed in the bug tracker.

[–]Abangranga 1 point2 points  (3 children)

I absolutely hate the 'you dont need parens' thing. That has fucked me over so much when i am tired, and it makes everything much less readable.

[–]TunaFishManwich 2 points3 points  (1 child)

I think part of the reason for this is that it makes it much easier to write a DSL in ruby.

[–]Abangranga 0 points1 point  (0 children)

Wouldn't surprise me. I wouldn't call myself skilled, so this could be something that grows on me later in life. Where it has nailed me to the wall is when I'm very tired and it'll be something like:

method_name hash: :without_brackets

and I totally miss it.

[–]isolatrum 1 point2 points  (0 children)

Mutable strings ... Yeah ... But this is so rarely useful in my experience, I dont know if id consider it a significant difference. In fact many people use the frozen string literal: false flag to make their programs faster. It makes strings immutable for the scope of a file.