R.I.P. Ruby Hash Rocket Syntax 1993-2010
blog.peepcode.com
blog.peepcode.com
In fact, like almost everyone else in our space we're looking to hire => http://hashrocket.com/jobs
:sex
Sex symbol, or colon sex?The jobs link is down.
(Of course, perhaps I'm more sensitive than most, having actually written an Ruby lexer - http://github.com/jasonl/eden - which made me deal with the dusty, hidden corners of the Ruby grammar.)
Ruby is just showing its Perl roots.
So => is the "hashrocket" operator? I can't find any reference to this name besides this: http://www.ruby-forum.com/topic/152544
{
key: "value",
dr_nic: "The Comedian",
ttl: 42
}
python will expect there to be a variable called 'key', one called 'dr_nic', and one called 'ttl'. If you intend to do d['ttl']
you'll need to create a hash using a literal string dict(
key="value",
dr_nic="The Comedian",
ttl=42
)
...but that is a bit different syntax.I was unaware that there was a difference in performance, but a quick timing check shows that it takes about twice as long to create a dictionary with 7 items using dict() than using {}.
It makes sense when I stop to think about what the code would have to do, though.
With the time it takes to assign with a literal normalized to 1, I'm seeing dict(kwargs) (with kwargs created before the benchmark) take 2 and dict(key_1=val_1, key_2=val_2, etc.) take 3.
>>> def dict1():
... {"a" : 1, "b" : 2}
...
>>> from dis import dis
>>> dis(dict1)
2 0 BUILD_MAP 2
3 LOAD_CONST 1 (1)
6 LOAD_CONST 2 ('a')
9 STORE_MAP
10 LOAD_CONST 3 (2)
13 LOAD_CONST 4 ('b')
16 STORE_MAP
17 POP_TOP
18 LOAD_CONST 0 (None)
21 RETURN_VALUE
>>> def dict2():
... dict(a=1, b=2)
...
>>> dis(dict2)
2 0 LOAD_GLOBAL 0 (dict)
3 LOAD_CONST 1 ('a')
6 LOAD_CONST 2 (1)
9 LOAD_CONST 3 ('b')
12 LOAD_CONST 4 (2)
15 CALL_FUNCTION 512
18 POP_TOP
19 LOAD_CONST 0 (None)
22 RETURN_VALUE
Given that, I'd assume that the overhead doesn't grow as the dict grows.Say you use a dict in a non-mutating manner, it is made up of string literals only, and you construct it within a loop; it could be able to be pulled out as an invariant, the same could be true of the function-call case, but potentially not as easily.
On invariant code motion: it tends not to be a big win because programmers usually move obvious big code out of loops explicitly. They do that because relying on a compiler optimization to do it is unpredictable; you may accidentally trigger a wall in your optimizer's ability to analyze your code as it grows more complex over time.
But we really are arguing over small potatoes here.
% python -m timeit '{"key": "value", "dr_nic": "The Comedian", "ttl": 42}'
1000000 loops, best of 3: 0.296 usec per loop
% python -m timeit 'dict(key="value", dr_nic="The Comedian", ttl=42)'
1000000 loops, best of 3: 0.771 usec per loop
There are far better ways to make code faster than worrying about the extra fraction of a microsecond you're spending calling dict rather than using the literal syntax.I always do in such way. And I will continue even if it's a bit slower =)
[Though I guess it could be a 'win' for Rails hackers since they are likely to just be Ruby/Rails+JavaScript hackers.]
Anyway, as a non-Ruby developer I can appreciate the move. Also reminds a bit of some Scheme's ability to prefix and postfix symbols with »:«.
>> a = :foobar
=> :foobar
>> {a => 1}
=> {:foobar=>1}I guess if you want to save some key strokes and you use symbols as keys you can use the new syntax.
The other cool/different thing about Hash in 1.9 is that it preserves order.
irb(main):001:0> hash={:"symbol with space" => nil}
=> {:"symbol with space"=>nil}
irb(main):002:0> hash={"symbol with space": nil}
SyntaxError: (irb):2: syntax error, unexpected ':', expecting tASSOC
hash={"symbol with space": nil}
^
from /usr/bin/irb1.9.1:12:in `<main>'
irb(main):003:0> hash={"symbolwithoutspace": nil}
SyntaxError: (irb):3: syntax error, unexpected ':', expecting tASSOC
hash={"symbolwithoutspace": nil}
^
from /usr/bin/irb1.9.1:12:in `<main>'
irb(main):004:0> hash={symbolwithoutspace: nil}
=> {:symbolwithoutspace=>nil}
irb(main):005:0> hash={symbol with space: nil}
SyntaxError: (irb):5: syntax error, unexpected tIDENTIFIER, expecting keyword_do or '{' or '('
hash={symbol with space: nil}The new syntax works only with conventional symbols (no quotes), not with any other key type, so no integers, strings, objects etc. For everything else, you can fall back to the original syntax. You can even mix then if you really feel the need (yech!)
That said, for almost all use cases hashes use symbols as keys, and for these cases the new syntax is much cleaner and I'm glad to see it
#main_page{:"data-role" => 'page'} #main_page{"data-role" => 'page'}
`data-` attributes are also special-cased: #main_page{data: {role: 'page'}} #main_page(data-role='page')
[1] http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html#ht...I'd say there are more vanilla symbol hash uses by number, but the most interesting and powerful things you can do with hashes don't involve symbols for keys, imho.
It would be one thing to just have a new symbol that was a drop-in alternative to what works now. But in this case you have a _sometimes_ alternative, so you need to remember a new rule, if only to be able read other people's code. That cognitive cost outweighs any keystroke benefit IMHO.
Are you a Ruby dev?
If so, is your concern based on the impact this has on you personally, or is it based on concern for some mythical other dev who might have problems with the additional "cognitive cost"?
I've been a developing in Ruby now for about 4 years, I think this (the new syntax) is absolutely the right thing to do
But, given the chance HN will always fan the flames of the argument that lies within.
[03:17:49 ~]$ irb
ruby-1.9.2-p0 :001 > { a: 10 }
=> {:a=>10} {
foo: 'bar',
hello_world: 'OH HAI',
}
to {
foo => 'bar',
hello_world => 'OH HAI',
}
If you try to do that with colons, it goes all wonky: {
foo : 'bar',
hello_world : 'OH HAI',
}Well, no.
Here's an example. File a:
{
foo => 'bar',
hello_world => 'OH HAI',
}
File a with a long key added, let's call it b: {
foo => 'bar',
hello_world => 'OH HAI',
longlonglonglong => 1,
}
Then we do a diff: $ diff -uw a b
--- a 2011-01-06 08:51:55.725595229 -0600
+++ b 2011-01-06 08:51:52.285595452 -0600
@@ -1,4 +1,5 @@
{
foo => 'bar',
hello_world => 'OH HAI',
+ longlonglonglong => 1,
}
So see, that's not a problem.{
key: "value",
dr_nic: "The Comedian",
ttl: 42
}didn't seem to use the :symbols I know but omitted the colon.
Turns out, it somehow works:
> a = {my_key: "my_value"}
=> {:my_key=>"my_value"}
Has this always been there? It's kinda weird because that way they look like regular variables O_o
I could probably rant on PHP syntax all day long, but it's too off topic here.
$x = array("a" => 100); // legal
$y = my_func("a" => 100); // illegalHaving written and shipped 5+ iPhone apps written in the verbosphemy* that is ObjectiveC, I'm strongly looking forward to being able to do all future iPhone apps in a saner language like JavaScript or Python. If I do them at all. Ever again. In fact it's pretty much a hard requirement. Just after this current one ships.
(*: verbose + blasphemy)
list.sorting = {:name => 'ASC'} to list.sorting = {:name: 'ASC'}
This does not work (although I think it could):
{"foo bar": 'b'}
but this does:
{:"foo bar" => 'b'}
Without being truly JSON compatible, it's only moderately useful.
One annoyance of Ruby for me has always been that many libraries, such as Rails, allow for symbols or strings interchangeably - but only in "most" cases, often leading to head banging in the other cases.
More intrigue: the syntaxes are also mixable
{foo: 'bar', "add-a" => 'dash', :'or a' => 'space in a symbol'}
is valid.
I'm confident it's commonly understood best practice (among many languages at that) to use an underscore (_) in lieu of spaces in such things.
I agree that the quirks themselves are odd, but the matter of using a key with a space in it really stood out.
:"Some Text" is a perfectly valid symbol.
ruby-1.9.2-p136 :001 > h = {:"Some Text" => 123} => {:"Some Text"=>123} ruby-1.9.2-p136 :002 > h[ "Some Text".to_sym ] => 123
Don't you get sick of typing function(){..} when just {..} would do?