Code Blocks - curly braces right? Wrong! brackets!?

Hi guys

So I want to improve my one liner ninja skills and execute a snippet of code given a condition

if a==b
puts a
puts b
end

I tried shortening to

{puts a ; puts b } if a==b

but that syntax errors on me

but THIS works? :slight_smile:

(puts a ; puts b) if a==b

So by analogy

5.times (puts a ; puts b)

should work right? No, it needs curly brackets here.

What am I missing - is my coding instinct fading?

Yeah, {} instead of () makes no sense to me. {} shouldn’t work because it’s block.
() works because it’s evaluating the code first.

The { } is similar to do end block but with higher precedence than do end.
For example:

{ 'hello' }     # SyntaxError

But:

def x(&block)
	block.call.tap(&method(:puts))
end

x { 'hello' }

Again,

('hello') will return hello [try that yourself in Interactive Ruby]
It’s like (5 + 5) * 2 where 5 + 5 will be evaluated first (which will return 10) then it will be multiplied with 2 (which will return 20).

So when you write { } first it makes no sense because no method is given.
Yes, any method can accept block. If it doesn’t yield or call the block, then the value in the block is kind of wasted. For example:

p('hello') { whatever }

The p method doesn’t work with a block. So when you put whatever here in the above example, NameError is not raised.

If you do puts('hello') then you are sending the ‘hello’ string to the method puts.
So in such case you can do:

puts((('hello')))
# Because (('hello')) also returns 'hello'

I don’t know if this makes much sense, but this is a syntax of Ruby. And I believe you are not supposed to put random stuff in random places and expect your code to run flawlessly…

ok - thanks for the comments. I think my mistake was confusing do … end with begin … end. So something like

begin puts 1; puts 2 end if a==b

works fine.

I thought begin…end is also a block, and blocks can be delimited with {} brackets. Seems not

Something like

a==b ? begin puts “=” end : begin puts “!=” end

works, but its ugly I just thought I could shorten it to

a==b ? { puts “=” } : { puts “!=” }

but it turns out

a==b ? ( puts “=” ) : ( puts “!=” )

is the way to go

Yes, {} are not begin end block. begin end block are for catching exceptions:
As an example, I am going to represent a code that downloads a random image from unsplash (if you run BASH shell, it will run fine):

#!/usr/bin/ruby -w
require 'net/https'
FILE = File.join(__dir__, 'image.jpg')

begin
	if File.exist?(FILE)
		Warning.warn "File #{FILE} already exists!... Press enter to continue..."	
		abort('Quitting!') unless STDIN.gets.to_s.strip.empty?
	end

	puts "Downloading an image!"

	chars = %w(| / - \\)
	t = Thread.new { loop while chars.size.times { |i| print("\e[2K#{chars.rotate![0]} Downloading#{?..*(i % 4)}\r") || sleep(0.1) } }

	Net::HTTP.get(URI('https://source.unsplash.com/random'))[/".*"/].undump
		.tap { |link| File.write(FILE, Net::HTTP.get(URI(link))) }

	t.kill
rescue SocketError, OpenSSL::SSL::SSLError
	STDERR.puts 'Seems like you have an internet issue!'
rescue Errno::EACCES
	STDERR.puts "Permission denied while trying to write to #{FILE}"
rescue SignalException, Interrupt, SystemExit
	STDERR.puts 'Operation terminated'
rescue Exception => e
	STDERR.puts "Caught an exception:\n#{e}"
else
	STDOUT.puts "Successfully saved a random image as #{FILE}"
ensure
	puts 'Bye!'
end

Note that the begin block initiates the code.

  • The first rescue block helps to catch exception when there’s no active internet connection.

  • The 2nd rescue block to catch exception raised by File.write when you have no permission to write a file to the current directory.

  • The 3rd rescue check if you are pressing [ctrl + c] or killing the program or not (all signals will be caught except signal 9 sigkill).

  • The 4th rescue block catches all other exceptions that are not mentioned in the above rescue blocks (you can replace Exception with StandardError)

  • The else block get executed if the code in the begin block succeeds or not.

  • The later ensure block ensures that your program prints ‘Bye’ to the terminal no matter what! (unless it receives sigkill, 9)


The begin end block and () let’s you do funny things as well, which are just bad practice in Ruby:

begin
	puts 'This is an endless loop!'
end while true

Or

(
	puts 'This is an endless loop!'
) while true

# This works because `puts 'This is an endless loop' while true` is also valid.

The above codes will run in an infinite loop, but this is a very bad practice. I don’t know about the () code here, but the begin end block’s purpose is not to imitate the do while in C! So it’s better to know, but don’t use the above code instead of traditional usage!


Hope you got the above. Nevertheless,

There’s another begin end block. This time it’s BEGIN {} END {}:

For the sake of example, consider this code:

#!/usr/bin/ruby -w
puts 'hi'

BEGIN {
	a = 5
	puts 'Ruby!!'
}

END { puts "Bye bye Mr. #{a}" }
puts 'See Ya!'

Output:

Ruby!!
hi
See Ya!
Bye bye Mr. 5

But the problem is you can’t directly use the {} or do...end without any method. You need to call a method before that. On the other hand the BEGIN {} and END {} will not accept do...end block because it’s a syntax rather than a method.


As you said:

a==b ? { puts "=" } : { puts "!=" }

Yes, this will work if you replace the { } with ():

a = b = 5
a == b ? (puts "=") : (puts "!=")    # prints =

# or because the ternary operator returns the first value when true, and the last value when false [just like `if then...elsif...else...end`:
puts a == b ? "=" : "!="    # prints =

Don’t call your code ugly. My code is uglier! :joy:
I would rather use ? for creating string as well! puts a == b ? ?= : '!=' # prints =

You shouldn’t worry unless Ruby is returning some warning about your syntax, because it’s fine. Rather than making code pretty, try to make it faster! Because if it’s the part of the community, then you shouldn’t worry about that too much…

https://docs.ruby-lang.org/en/trunk/syntax/literals_rdoc.html

Hope this was fun and not over-complicated…