What are => and : in Ruby? - ruby

When we're defining a Hash in Ruby, there's several syntax we can use:
# IRB Ruby 2.7.1 - Defining a Hash
my_hash = { 'string_transformed_into_symbol_1': 'value',
"string_transformed_into_symbol_2": 'value',
'string_1' => 'value',
"string_2" => 'value',
symbol_1: 'value',
:symbol_2 => 'value' }
I was thinking that the arrow => was from Ruby 1.8 and was replaced by the colon :. Though, from the example above, we can see that it is not only a syntactic sugar: : transforms a key String into Symbol:
# IRB Ruby 2.7.1 - Outputing a Hash
my_hash
=> { :string_transformed_into_symbol_1 => "value",
:string_transformed_into_symbol_2 => "value",
"string_1" => "value",
"string_2" => "value",
:symbol_1 => "value",
:symbol_2 => "value" }
From this question, mu is too short's great answer stated:
The JavaScript style (key: value) is only useful if all of your Hash keys are "simple" symbols (more or less something that matches /\A[a-z_]\w*\z/i
[...]
But you still need the hashrocket if your keys are not symbols.
This makes me consider : and => as methods (but I know I'm wrong). However, I couldn't find anything on the documentation about it.
Q: What exactly are : and => ? How could I learn more about them, and where are they defined in Ruby's source code ?

Lexer/Parser Tokens
The symbols you're referencing aren't methods or operators, they are lexer/parser tokens used to interpret the syntax of your source code. The hashrocket is defined as the tASSOC association token, which is used to associate things such as key/value pairs or exception stack traces.
The colon has several uses in Ruby, but IIRC Ruby 2.x introduced the postfix colon as syntactic sugar for tASSOC when the left-hand side is a Symbol. I'm less sure about how the token is defined or parsed in complex cases—assoc is the most likely bet for this example—but for practical purposes you can simply think of a: 1 as semantically equivalent to :a => 1.
You can also use Ripper#sexp to examine your source code to see how the lines will be parsed by the interpreter. For example:
require 'ripper'
pp Ripper.sexp "{a: 1}"
[:program,
[[:hash,
[:assoclist_from_args,
[[:assoc_new, [:#label, "a:", [1, 1]], [:#int, "1", [1, 4]]]]]]]]
#=> [:program, [[:hash, [:assoclist_from_args, [[:assoc_new, [:#label, "a:", [1, 1]], [:#int, "1", [1, 4]]]]]]]]
pp Ripper.sexp "{:a => 1}"
[:program,
[[:hash,
[:assoclist_from_args,
[[:assoc_new,
[:symbol_literal, [:symbol, [:#ident, "a", [1, 2]]]],
[:#int, "1", [1, 7]]]]]]]]
#=> [:program, [[:hash, [:assoclist_from_args, [[:assoc_new, [:symbol_literal, [:symbol, [:#ident, "a", [1, 2]]]], [:#int, "1", [1, 7]]]]]]]]
In both cases, you can see that the S-expression is using the colon to build an "assoc_new" subexpression. For further drill-down, you'd have to refer to the Ruby source tree.
See Also
lexer.rb
parse.y

The : and => symbols are part of Ruby's language syntax, i.e. something way below the conceptual level of methods or objects.
In the language implementation, they are recognized by the parser. As such, the parser sees your source code, parses it and (in this case) creates objects based on the source code. Specific syntax elements result in various objects to be created, e.g. if you write 3.14 in your source code, a new Float object will be created. Similarly, the defined syntax for creating Hashes as recognized by Ruby's parser defines various options to describe Hashes in your source code, e.g.
{ :symbol => 'value' }
{ symbol: 'value' }
{ 'symbol': 'value' }
Each of those options will create the same hash, namely one with a symbol key and a string value.
The first syntax option was the only original syntax to define Hashes. The others were introduced later to give developers more options to define hashes in a more "natural" looking way.

Related

In the future, can I use the new hash syntax with array as key?

I'm creating a hash with an array as key, e.g.:
versions = { [1, 2, 3] => 'some-code-name' }
I would like to do the same but using the new hash syntax, which raises an error as of now:
versions = { [1, 2, 3] : 'some-code-name' }
# => SyntaxError: (irb):18: syntax error, unexpected ':', expecting =>
I would like to know if they will implement it in the future (just because it is syntactic sugar).
There's an answer explaining that:
This syntax is only for Ruby 'symbols', and is an alternative to the common usage:
:symbol => 5
rather than as a general key. More on symbols here. And others have written about this with respect to the principal of least surprise (see here).
But I have been able to do the same with a string and thought that maybe they would extend this functionality to be globally available within hashes.
But I have been able to do the same with a string ...
I think you are referring to the "name": value syntax.
Let's see:
{"foo": 123} #=> {:foo=>123}
Notice what happens to the "string"? It becomes a symbol.
Within a hash literal "foo": value is a shortcut for :"foo" => value and :"foo" is in fact a symbol:
:"foo" #=> :foo
I would like to know if they will implement it in the future (just because syntactic sugar)?
Who knows? But I don't think so.

What are all the different uses of the [square brackets] in Ruby?

I'm coming across the square bracket [] syntax quite a bit in Ruby, but it never seems to be doing the same thing. Can anyone list all the different uses for the square brackets [] in Ruby so my mind can get a handle on this seemingly endlessly versatile little symbol? (How is it possible that one symbol can do so much without the Ruby interpreter getting confused?)
Examples:
[] and []= methods
%q[...]
[1,2,3][0]
hash["a"] = 3
ary = []
/[^A-Fa-f0-9]/
"Is a string"[5,3]
The square brackets are in two strict contexts and one optional one:
Defining Arrays
Arrays, i.e. a data structure providing and ordered list of elements can be specified in code by using a syntax like [1,2,3]. This creates an array with the three elements 1, 2, and 3 in exactly that order. you can then iterate over the array using on of the iterator functions like each or map or you can directly access a specific elements by its index id as shown below.
Accessing Elements in Arrays and Hashes
Hashes (also called hashmaps, dictionaries, or associative arrays in other languages) also contain elements similar to arrays. The are different from this in the way that they store their data unordered. Data is not accessed by an integer id as is the case by arrays but with an arbitrary key (commonly a symbol or a string). This is different from e.g. PHP where the same Array type is used for both.
This access to the data is facilitated by methods called [] and []= for both hashes and arrays.
my_array = [:a, :b, :c]
second_element = my_array[1]
# => :b
# notice that the first element in arrays always has the index 0
my_hash = {:a => 1, :b => 2, :c => 3}
element_of_b = my_hash[:b]
# => 2
This is the common use case for the brackets. In Ruby code, you might sometimes see other classes implementing the bracket functions. They do so to allow an access similar to either arrays or hashes and it is then generally expected that these classes behave similar to those but this is in no way enforced. See also Duck Typing.
% Notation
Ruby has a third syntax to create strings (and other objects) apart from the common. Using this syntax, the literal string in code are not enclosed by " or ' but use a special delimiter. It starts with a percent sign, a single character specifying the object to be created and almost any character to chose as a delimiter:
a = %w[foo bar baz]
b = %w{foo bar baz}
c = %wxfoo bar bazx
d = ["foo", "bar", "baz"]
All three example create the same array. Please see the some documentation on how to use this syntax and which other modifier characters are available in Ruby.
While it is common to use brackets here, it is on no way required and can be substituted if required. It is just advisory here as the most common usage of this notation is to create an array of elements from a whitespace-seperated string (as seen above). As such, the usage of brackets makes it further clear that an array is returned as the syntax looks similar to the basic array specification.
Okay, just for my own notes I have gone and had a closer look at this and, building on Holger Just's answer, come up with the following: the use of square brackets in Ruby can be divided into 6 uses, 3 of them a part of Ruby's method definitions and 3 of them semantic constructs.
Method definition
Object creation via class methods Array::[], Hash::[]
Array.[](1,2,3) #=> [1,2,3] #Un-sugared notation
Array["a","b","c"] #=> ["a","b","c"] #Sugared equivalent
Hash["a", 100, "b", 200] #=> {"a"=>100, "b"=>200}
Nothing to do with literal constructors, although it does the same thing.
Element reference via instance methods Array#[], Bignum#[], Continuation#[], Fixnum#[], Hash#[], MatchData#[], Method#[], Proc#[], String#[], Struct#[], Symbol#[], Thread#[], and class methods Dir::[], ENV::[]
ary = [1,2,"abc", [15,16,[26,27]]]
ary.[](2) #=> "abc" #Un-sugared notation
ary[2] #=> "abc" #Sugared equivalent
ary[0,2] #=> [1,2]
ary[3][2][1] #=> 26
[1,2,3][0] #=> 1
"Is a string"[7,3] #=> "rin"
Element assignment via instance methods Array#[]=, Hash#[]=, String#[]=, Struct#[]=, Thread#[]=, and class method ENV::[]=
ary = [1,2,3]
ary.[]=(1,"abc") #=> [1,"abc",3] #un-sugared notation
ary[2] = "def" #=> [1,"abc","def"] #Sugared equivalent
hash = {"a"=>1, "b"=>2}
hash["a"] = 3 #=> {"a"=>3, "b"=>2}
Semantic constructs
Object creation via the array literal constructor
ary = []
There are a bunch of literal constructors in Ruby that create an object of the relevant class via the use of (usually) a simple symbol pair, square brackets being the literal constructor for array objects: Array [], Hash {}, Proc ->(){}, Range .. and ..., Regexp //, String "" and '', Symbol : and :"".
Object creation via the % notation
%q[hello there you] #=> "hello there you" # String % notation
%w[hello there you] #=> ["hello", "there", "you"] # Array % notation
It is not, strictly speaking, square-bracket notation, but rather two-symbol-pair notation of which you can use square brackets if you wish. So %q#hello there you# is equally valid.
Ruby's regular expressions
/[^A-Fa-f0-9]/
Square brackets indicate character classes in Ruby regular expressions.
I did find another use of the [], as a pattern for use in the Dir::glob method, but its supposed to act exactly as it does in regular expressions. Still, it indicates that there are possibly more uses hidden away in Ruby's 1500+ methods.
handy syntax for instantiating structs with square brackets
irb(main):001:0> Point = Struct.new(:x, :y)
=> Point
irb(main):002:0> point = Point[1,2]
=> #<struct Point x=1, y=2>
irb(main):003:0> point.x
=> 1
irb(main):004:0> point.y
=> 2

Inconsistent implicit hash creation in Ruby?

Ok, so I was comparing some stuff in my own DSL to Ruby. One construct they both support is this
x=["key" => "value"]
Knowing the difference between arrays and hashes, I would think this to be illegal, but the result in Ruby is
[{"key" => "value"}]
Why is this? And with this kinda syntax why can't you do
x=("key" => "value")
Why is an array a special case for implicitly created hashes?
Another special case is in a function call, consider:
def f(x)
puts "OK: #{x.inspect}"
end
f("foo" => "bar")
=> OK: {"foo"=>"bar"}
So in some contexts, Hashes can be built implicitly (by detecting the => operator?). I suppose the answer is just that this was Matz's least-surprising behavior.
With this apparent inconsistency in implicit hash creation, ruby achieves consistency in this regard:
func(whatever...)
can always be substituted with:
args = [whatever...]
func(*args)
You can convert between argument lists and arrays, and therefore it is logical that they have the same syntax.
I would say that the interpreter figures out that "key" => "value" is a hash, the same way it would figure out that 5 is a number when you put it into an array.
So if you write:
x = [5]
The interpreter is not going to think that it is a string, and return:
x = ["5"]
It seems that ruby implicitly creates hashes in some instances.

Ruby => operator... eg: :text => /Log In/

What does this do? I find the example here, but other than what it does, what does it mean? I can't find anything on google because well, I am not sure what '=>' is even called in this context.
More examples here:
http://mechanize.rubyforge.org/mechanize/EXAMPLES_rdoc.html
In :text => /Log In/, you are passing a hash to page's link_with function, and the key in the hash is :text and its corresponding value is /Log In/.
Basically: :x => y means that :x is a key in a hash that maps to a value of y.
passing hashes to functions like this allows you to have something like (but not exactly) named parameters.
UPDATE:
A symbol of the form :something is called.... a symbol! You can think of them sort of like global string constants (but they're not quite the same). Now, when you think back to something like:
login_page.form_with(:action => '/account/login.php')
What you're actually doing is constructing a new hash on the fly. You create a new element in the hash, where the key is a string with the value "action", and the value of that element is "/account/login.php" (in this case, also a string, but I'm pretty sure you can store other things in hashes besides strings).
...whew! It's been a while since I've worked with Ruby. I hope I got that all correct. ;)
Some good looking pages here (more can be found by searching google for "ruby symbol")
http://glu.ttono.us/articles/2005/08/19/understanding-ruby-symbols
http://www.troubleshooters.com/codecorn/ruby/symbols.htm#_What_are_symbols
It associates a value with an index for hashes.
obj.method :text => /Log In/
is shorthand for
obj.method {:text => /Log In/}
It's used to create a hash expression, as in { key => value }.
Also, when used as the last parameter in a method call, the { } are not needed, so the key => value can appear alone.
>> p({:a => 1, :b => 2})
{:a=>1, :b=>2}
=> nil
>> p :c=>3, :d=>4
{:d=>4, :c=>3}
=> nil
>> t = { :e=>5, :f=>6 }
=> {:f=>6, :e=>5}
This shorthand is really nice in poetry mode, where a literal hash following a method name would look like a block.

Turning a hash into a string of name-value pairs

If I'm turning a ruby hash into a string of name-value pairs (to be used in HTTP params, for example), is this the best way?
# Define the hash
fields = {"a" => "foo", "b" => "bar"}
# Turn it into the name-value string
http_params = fields.map{|k,v| "#{k}=#{v}"}.join('&')
I guess my question is:
Is there an easier way to get to http_params? Granted, the above way works and is fairly straightforward, but I'm curious if there's a way to get from the hash to the string without first creating an array (the result of the map method)?
Rails provides to_query method on Hash class. Try:
fields.to_query
From the The Pragmatic Programmer's Guide:
Multiple parameters passed to a yield
are converted to an array if the block
has just one argument.
For example:
> fields = {:a => "foo", :b => "bar"}
> fields.map { |a| a } # => [[:a, "foo"], [:b, "bar"]]
So your code could be simplified like this:
> fields.map{ |a| a.join('=') }.join('&') # => "a=foo&b=bar"
This is probably the best you can do. You could iterate through the pairs in the hash, building the string as you go. But in this case the intermediate string would need to be created and deleted at each step.
Do you have a use-case where this is a performance bottleneck? In general Ruby is doing so much work behind the scenes that worrying about a temporary array like this is probably not worth it. If you are concerned that it may be a problem, consider profiling your code for speed and memory usage, often the results are not what you expect.
In Rails it also had a method to convert hash to params.
Example1: Turn a hash to params
{ param1: "value1", param2: "value2" }.to_query
Result:
"param1=value1&param2=value2"
Example2: Turn params to hahs
params = "param1=value1&param2=value2"
Rack::Utils.parse_nested_query(params)
Result:
{"param1"=>"value1", "param2"=>"value2"}

Resources