Ruby opinions (111)

1 Name: #!usr/bin/anon 2005-11-23 17:55 ID:rgxG1g8s

I am learning Ruby right now, to play with Ruby on Rails, and I don't know yet wether I like it or loathe it, it looks pretty much like the illegitimate child of Perl and Python, which sounds very wrong.

What are your opinions about Ruby?

62 Name: #!/usr/bin/anonymous : 2007-08-07 23:53 ID:7jt9w/a2

Objective-C, Fuck yea!

63 Name: dmpk2k!hinhT6kz2E : 2007-08-08 05:41 ID:Heaven

All right. I've been meaning to post this for a while, so:

Today's Q (and A?)

I don't like how Ruby handles optional arguments. Let's first take a look at a language that does it decently:

>>> def x(a = 1, b = 2):
... return a * b
...
>>> x()
2
>>> x(a = 2)
4
>>> x(b = 3)
3
>>> x(2, 3)
6
>>> x(b = 3, a = 1)
3

So, let's take a look at the above. I can pass any optional parameter, and I can do it in any order. If I pass it in proper order I don't even need to include the names. Makes sense, right? Let's try that with Ruby:

001:0> def x( a = 1, b = 2 )
002:1> a * b
003:1> end
=> nil
004:0> x()
=> 2
005:0> x( a = 2 )
=> 4
006:0> x( b = 3 )
=> 6

Whoa. How does that last one work? 1 * 3 = 3, not 6!

Ruby's "optional" parameters are positional. So what's happening above is that Ruby is creating a new variable at the call site (e.g.: the a in x(a = 2)), assigning an object to it (the 2), and then returning the result of the expression (the same object: 2) which is then used as the argument to x().

Thanks to Ruby foolishly not having borrowed Perl's great idea of lexical scoping with strict mode, I'm sure any number of Ruby neophytes have been left scratching their heads. As an aside I'll just add that all dynamically-typed languages should borrow that idea, but it seems some language communities are too busy feeling smug and superior, and they completely miss an a lot of gems hidden inside Perl's terrifying exterior. If I had to write something big in a dynamically-typed language today, I'd reach for Perl first, and the above is only one of the reasons.

To continue: optional parameters that are only positional are quite anemic. In fact, their only real use is if you need a quick refactor of a method to add some extra functionality without breaking old code -- if the signature doesn't already include an optional parameter on the end, you can just tack one on. Of course, that doesn't scale at all, because it's impossible to give an optional parameter in the 5th position if the 4th is also optional, unless you also pass the 4th as well. This is brain-damaged, so you're up the creek if you want more than one optional parameter, bub.

How do you really do it in Ruby? Apparently like this:

001:0> def x( options = {} )
002:1> a = options[ :a ] || 1
003:1> b = options[ :b ] || 2
004:1> a * b
005:1> end
=> nil
006:0> x()
=> 2
007:0> x( :a => 2 )
=> 4
008:0> x( :b => 3 )
=> 3
009:0> x( 2, 3 )
ArgumentError: wrong number of arguments (2 for 1)
from (irb):9:in `x'
from (irb):9
from :0

Whoops. I guess once you start down that path, you have to use :a and/or :b symbols every time you want to pass an argument; no more positional shortcuts, which Python allows. So now we have a more verbose hash with required unpacking inside the method -- an idea Ruby shouldn't have borrowed from Perl -- and the call site is likewise a bit longer.

Does anyone who has better familiarity with Ruby know a less ugly way to do this? I'd prefer it to have named optional parameters which are also positional, but I'll settle for anything less.

64 Name: dmpk2k!hinhT6kz2E : 2007-08-08 05:59 ID:Heaven

Anybody reading this thread might be convinced I detest Ruby.

But that ain't so!

So I'll share one feature of Ruby I wish I saw in more languages: ||=

||= is a cute little feature that checks whether a variable is nil, and assigns what's on the right-hand side if it is. So things like this:

if x

x += [ y ]

else

x = [ y ]

end

...can become this:

x ||= []
x += [ y ]

Five lines into two, and I suspect the latter is also a tad faster. I find myself often using things like "@@var ||= <x>" when caching the results of some expensive operation that's needed in a frequently-invoked method, and while the above example might seem a big usual (why not just x = [] before a loop?), I seem to get it a lot when processing arrays that contain arrays.

I cannot emphasize how much I like the ||= operator. It's a small thing, but oooooh aaaaaah. Now I just wish there were things like ||+= et alia...

65 Name: dmpk2k!hinhT6kz2E : 2007-08-08 06:00 ID:Heaven

This is what happens when you don't preview. D:

66 Name: #!/usr/bin/anonymous : 2007-08-09 13:33 ID:Heaven

>>64
perl has ||= and //=...

67 Name: dmpk2k!hinhT6kz2E : 2007-08-09 17:03 ID:Heaven

I've never seen it. Is that Perl6?

What's the difference between the two?

68 Name: #!/usr/bin/anonymous : 2007-08-10 15:47 ID:JcCwsY9C

Perl has always had things like $param=$query->param("name") || "default". ||= is just the usual operator shorthand.

69 Name: Eleo : 2007-08-22 02:32 ID:qHyYC9ii

>>63
What is so wrong about passing arguments in the order you specified in your own methods? Surely it requires slightly more memory (in your head), but it's not difficult to remember unless you have a vast list of arguments, in which case I think it's neater to use a hash anyway.

Furthermore, Ruby's way of handling it can be looked at as very useful useful. One might want to create a new variable and pass it as an argument at the same time.

70 Name: dmpk2k!hinhT6kz2E : 2007-08-22 05:28 ID:Heaven

> What is so wrong about passing arguments in the order you specified in your own methods?

Nothing, provided it's also possible to name them. If you can't, like with Ruby, there's a problem. Consider the following:

def arg(optional1 = x, optional2 = y, optional3 = z)

Now, how do I pass only optional3 to arg()?

It seems I can't. I have to pass optional1 and optional2 as well, which increases coupling. This behaviour removes what I consider the greatest benefit of optional arguments in the first place, and for no gain.

Ruby's hashes are an unwieldy and quite verbose way to circumvent this.

> One might want to create a new variable and pass it as an argument at the same time.

That's rare. When you need it... use a hash, either explicitly or like Python does with its varargs.

Python's argument passing leaves something to be desired too, but that's a different story.

71 Name: Eleo : 2007-08-22 23:49 ID:qHyYC9ii

I can see where you're coming from.

I don't know what to say since I've never run into that problem in particular where a hash wasn't more suitable. Then again, I've never run into a situation where I wanted to create and pass a variable simultaneously, but I still like the idea and imagine either would be useful in the right context. It would be great if both worked, IMO, although I'm not sure what that would look like syntactically.

72 Name: dmpk2k!hinhT6kz2E : 2007-08-23 05:14 ID:Heaven

It might look like Python, although I think it's also not ideal:

>>> def fun(arg1, arg2=3, arg3=7, **kwargs):
... print arg1, arg2, arg3,
...
... for name in kwargs:
... print kwargs[name],
...
>>> fun(1)
1 3 7
>>> fun(1, 2, 3)
1 2 3
>>> fun(1, arg3=5)
1 3 5
>>> fun(1, arg4=21)
1 3 7 21
>>> fun(1, woo=1, hoo=2, looky=3, here=4)
1 3 7 2 1 4 3

arg1 is a required argument. arg2 and arg3 are optional. *args is a dictionary (read: a hash) that catches overflow arguments (an example being the use of arg4=21).

In case you're wondering what happened to the ordering of the last four digits in the last example, that's because dictionaries aren't ordered.

Also, while I've never made use of keyword arguments in Python, I saw a plethora of them in other people's code where I last worked.

74 Name: fred : 2007-09-17 21:37 ID:i6CuOwit

A few monthes ago I gave a look to Ruby, because I wanted to give a try to RoR. I felt the syntax being really exciting. What made me give it up is the fact that the interpreter is, for now, very slow for me. So when Ruby have a fast interpreter I'll be happy to dive in it again :)
For now I'm sticking on Lua and Squeak/Smalltalk.

Happy coding,
Fred

75 Name: dmpk2k!hinhT6kz2E : 2007-09-18 06:42 ID:Heaven

Lua is a nice piece of software. I'm actually quite impressed by it, both as a language and as an implementation. Then there's LuaJIT which also adds support for coroutines across C function calls.

It's a bit unfortunate there isn't any MVC or routing in the Kepler project yet, otherwise I'd seriously consider using Lua for webdev. There are a couple projects I'd like to do that could use a fast interpreter.

76 Name: #!/usr/bin/anonymous : 2007-10-02 13:04 ID:RQXmFWxa

Wait. Wasn't lua the language with the extreme script language focus? To the point where it would parse floating-point literals according to locale? So that if you were, say, running a German locale, floats would have to be written as "5,67".

I just heard this rumour about Lua 5.0. That was a while ago, of course.

77 Name: #!/usr/bin/anonymous : 2007-10-03 03:56 ID:Heaven

You mean Lua 5,0.

I don't think that's true.

78 Name: dmpk2k!hinhT6kz2E : 2007-10-03 17:25 ID:Heaven

I haven't seen anything like that either.

It'd probably conflict with the rest of the grammar.

79 Name: #!/usr/bin/anonymous : 2007-10-05 13:20 ID:jysSx7tW

Lua seem far too streamlined to bother implementing anything like that.

80 Name: #!/usr/bin/anonymous : 2007-10-06 16:31 ID:c3WTWrjl

The main people anyone likes Lua is that it's fast.

But if it's really so fast, people would just write a Ruby to Lua compiler. Then your Ruby app still looks good but runs as fast as a Lua script. Everybody wins!

81 Name: dmpk2k!hinhT6kz2E : 2007-10-06 20:20 ID:Heaven

It doesn't work that way. The Lua VM is targeted quite specifically to the semantics of the Lua language.

You could come up with bytecode that'd emulate Ruby behaviour, but it'd be slower. For example, Lua's strings are immutable, in part due to the hashing and sharing that goes on underneath. Ruby's strings are mutable. Emulating mutable strings with immutable ones will impose significant overhead.

Also, I think that Lua is -- purely as a language -- superior to Ruby. Ruby contains all sorts of strange design decisions which make me believe that Matz was (hopefully he's better now) a haphazard cherry-picker who wasn't very good at synthesizing ideas. There are a number of good ideas in Smalltalk and Perl he never used, and a few half-assed implementations of those he did.

By comparison Lua's syntax is quite simple, it has proper lexical scoping and closures, and those tables are amazingly versatile -- they're used for arrays, lists, hashes, sets, even objects and modules.

However, it doesn't look like Lua will be leaving its stronghold as an embedded language, which limits its usefulness as a general-purpose language. It doesn't have regular expressions, standard and external libraries are limited, support for Unicode doesn't extend much beyond 8-bit clean strings (to be fair, the same problem with Ruby), there is no bignum support, there are no bitwise operators... and so on.

82 Name: #!/usr/bin/anonymous : 2007-10-06 23:52 ID:Heaven

>>77
>>78
Yeah, it was a rumour I read on some blog or another. Perhaps it's telling that I found it totally believable.

83 Name: #!/usr/bin/anonymous : 2007-10-07 02:20 ID:dpmbOmpN

otoh, lua differentiates between statements and values - even though they're syntactically unambiguous. this makes using the lua repl obnoxious and verbose. lua's debugger is run-time-only and is useless for incremental development- which seems to me to be the best possible and imaginable use of a "scripting" language.

finally, lua changes the C-visible API incompatibly in minor versions making lua a moving target.

84 Name: #!/usr/bin/anonymous : 2007-10-07 22:35 ID:Heaven

They don't release so-called "minor" versions too often, though.

85 Name: #!/usr/bin/anonymous : 2007-10-08 01:05 ID:LtfQPuwO

RoR is overrated. Django is much better IMHO.

86 Name: dmpk2k!hinhT6kz2E : 2007-10-09 17:50 ID:Heaven

>>85
Don't leave us dangling. Elaborate.

87 Name: #!/usr/bin/anonymous : 2007-10-14 02:41 ID:c3WTWrjl

Django fails largely due to the language it is implemented on.

88 Name: #!/usr/bin/anonymous : 2007-10-14 10:29 ID:Z3+PkUeX

89 Name: #!/usr/bin/anonymous : 2007-11-04 08:37 ID:6kOzKjm7

Guys, guys, you've got it all wrong; Ruby is a goddamn fucking blast! Ruby is the best!

90 Name: #!/usr/bin/anonymous : 2007-11-04 08:43 ID:Heaven

I really think Ruby does some awkward shit, like the things dmpk2k has been discussing, but I have to use PHP at work and I'd much rather be using Ruby.

91 Name: c:\4-ch\anonymous.exe : 2007-11-04 12:16 ID:Heaven

I'd be interested in ruby if there was less hype. Sounds odd, but the WEB TWO-POINT_OHHH bullshit ruined it for me.

92 Name: #!/usr/bin/anonymous : 2007-11-04 15:03 ID:Heaven

>>90
i've been doing php at work and i'd prefer to use brainfuck.
What you'd prefer over php doesn't say much about it.

93 Name: #!/usr/bin/anonymous : 2007-11-04 22:11 ID:6kOzKjm7

plugs ears Can't hear you guys, I'm having too much fun programming Ruby.

94 Name: #!/usr/bin/anonymous : 2007-11-05 02:30 ID:uoVr7v/2

>>86
Very fast (No need to rewrite shit in C if your site becomes popular) because of Psyco JIT and some awesome new caching features that have been recently added, much more extensible (If it's written in Python, you can easily find a way to incorporate it), top notch FREE documentation at the official site (Instead of "BUY THE BOOK, LOL")

Defining your model in Python then auto-generating the SQL vs auto-generating a model based of the given SQL in Ruby I guess up to personal preference, though beginners would find it much easier to go with the former. There isn't much you can't do in Django database wise, if you absolutely have to do something really tricky you still easily use your own SQL code.

Also using generic view functions seems a lot more sensible and maintainable instead of dumping scaffolding code everywhere. Django lets you define your own URL structure based on Regexes, where as Ruby forces you name your structure after your functions.

Python vs Ruby is a whole other argument, however for me the clear and readable syntax of Python makes reading, writing and maintaining code very easy. I can understand why existing MASTER PERL/LISP PROGRAMMERS would want to use all the funky features of Ruby though.

They're both excellent frameworks, though RoR gets much more and Django much less hype then they deserve IMHO.

95 Name: dmpk2k!hinhT6kz2E : 2007-11-05 03:51 ID:Heaven

> because of Psyco JIT and some awesome new caching features

I've noted the caching in Django, which is one reason why I'm more interested in it compared to, say, Pylons.

Psyco JIT is a bit of a joke for webdev though. While it's great in some other scenarios, I highly doubt it makes more than a few percent difference in a web app. I once used it to try and speed up a non-trivial Zope3 application, and the difference was less than 10%.

> auto-generating a model based of the given SQL in Ruby

There's no SQL in a migration unless you're doing something the ORM doesn't handle by default (some of which are unfortunately pretty basic things to be missing). I presume you're saying that you don't even need a migration?

> Also using generic view functions seems a lot more sensible and maintainable instead of dumping scaffolding code everywhere.

Could you elaborate a bit on this?

96 Name: #!/usr/bin/anonymous : 2007-11-05 23:26 ID:Heaven

f(x = 5 + 6)
puts x # prints 11

Is there honestly a good reason Ruby allows this?

97 Name: #!/usr/bin/anonymous : 2007-11-05 23:39 ID:Heaven

>>96

x=5+6 is just an expression- like any other. It isn't a named parameter, if that's what is confusing you.

98 Name: #!/usr/bin/anonymous : 2007-11-06 11:31 ID:c3WTWrjl

A lot of the performance problems people have with scripting languages are at startup time too, which is also practically irrelevant for web applications (or any other long-running application.)

Ruby is like this too, the Rails console takes quite some time to start up (I guess it has to load Ruby, a portion of the stdlib, then Rails dependencies, then the app...) but the web application starts when you start the web server and each request is plenty fast enough.

>>94
Dumping scaffolding code everywhere? I take it you mean the ONE LINE OF CODE you put in the controller in order for it to magically work without creating even a single view or a single controller method. Generating scaffold files is optional, and something you do only if you want to modify it somehow.

>>96
Because others do? Personally I find it messy but this sort of chaining has been useful in the past.

99 Name: #!/usr/bin/anonymous : 2007-11-06 12:33 ID:jysSx7tW

>>96

Here's the reason:

x=y=z=0

100 Name: #!/usr/bin/anonymous : 2007-11-06 22:13 ID:HR3eQWH6

>>95

> ...to try and speed up a non-trivial Zope3 application...

There is your problem.

Also: 100GET

101 Name: dmpk2k!hinhT6kz2E : 2007-11-07 06:24 ID:Heaven

No need to tell me. 18 months of Zope3 has left me with deep loathing for anything related to it. I'm still having trouble going near Python because of memories of Zope.

102 Name: #!/usr/bin/anonymous : 2007-11-07 16:46 ID:Heaven

http://mail.python.org/pipermail/python-dev/2003-May/035727.html

You're doing it wrong. Eliminate your startup by only doing it once- dump core, and unexec the result.

103 Name: #!/usr/bin/anonymous : 2007-11-10 01:08 ID:6kOzKjm7

I think you guys are all forgetting Ruby's fun factor here.

104 Name: #!/usr/bin/anonymous : 2007-11-10 10:16 ID:Heaven

>>102 has a good point, this was a common trick back when people were complaining about Perl being slow.

Of course I don't know about it being very cross-platform.

105 Name: dmpk2k!hinhT6kz2E : 2007-11-10 21:11 ID:Heaven

Let's talk Ruby's blocks, since they're a feature that is routinely brought up as a point in Ruby's favour. Since the rest of Ruby has broken lexical scoping and no first-class functionsmethods -- a rant for another day if someone asks -- blocks are Ruby's claim to fame.

Ruby borrowed the idea of blocks from Smalltalk. That was probably a good idea. Unfortunately, like almost everything else in Ruby, they're half baked.

First up, lexical scoping. Blocks come pretty close to behaving as they should here. Just one problem:

y = 0                     
[ 'o shi' ].map { |y| puts y } => 'o shi'
puts y => 'o shi' (should be 0)

Given that the argument to the block, y, is in a different scope to the y outside, you'd expect that after this chunk of code is executed, y would still be 0. No, it's 'o shi'. So arguments to a block stomp all over variables in outer scopes.

The next (much smaller) problem is that blocks are not first-class objects:

x = { |y| }
SyntaxError: compile error
(irb):1: syntax error, unexpected '|', expecting '}'
x = { |y| }
^
from (irb):1

Nope.

In Smalltalk they were first-class objects, but in Ruby procs are. One consequence is that there's no way to pass multiple blocks to a method short of converting them to procs first. Why the nuance? Who knows!

(I suspect it's partly because Ruby's grammar is foobar and uses {} for both blocks and hashes. The more worrisome part is that the performance characteristics are substantially different, which possibly hints at different means of implementation.)

See >>50 for another reason why I am not in favour of this in Ruby. If you want an example of a language that has the whole lexical scoping and first-class everything right, see Lua.

106 Name: dmpk2k!hinhT6kz2E : 2007-11-18 03:31 ID:Heaven

Another annoyance. This is a constant:

VAR = 0

Constants shouldn't change, right?

VAR += 1
(irb):2: warning: already initialized constant VAR

...okay, so you can change it, but there's a warning. Livable, I suppose. But what if it's not a scalar?

VAR = []
VAR.push 1 => VAR now equals [1]

Alas, this little stupidity means I can't do things like the following, since they're all now pointing to -- and can mutate -- the same object:

DEFAULT = [ 0, 0, 0, 0 ]
var1 = DEFAULT
var2 = DEFAULT
var1[0] = 1 => var2 is now -also- equal to [ 1, 0, 0, 0 ]

There are a few alternatives, two being: use .freeze() on the array when assigning to DEFAULT, or use .dup() when assigning to var1 or var2. The problem with freezing the array is that I can no longer mutate var1 or var2 since they're pointing to DEFAULT's frozen array; this defeats the purpose of creating DEFAULT. dup() has its own problems, for example:

DEFAULT = [ [0, 0, 0], [0, 0, 0] ]
var1 = DEFAULT.dup
var1[0][0] = 1

Guess what that does?

There are a number of ways around this. The one I've settled on for now is to create a method named DEFAULT() that returns a new copy of what I want. E.g.:

def DEFAULT
[ 0, 0, 0, 0 ]
end
var1 = DEFAULT()

Nevertheless, Ruby's constants aren't really constant for anything beyond a scalar.

This and a couple other experiences have finally given me an opinion on implicit pass-by-reference semantics: they're a pain. Perl and C have the right idea: the semantics are pass-by-value; if you want a reference or pointer, you know how to get it.

(NB: those of you who want to argue about passing large structures with pass-by-value semantics please see what OS's have been doing for decades: copy on write.)

107 Name: #!/usr/bin/anonymous : 2007-11-24 13:56 ID:c3WTWrjl

Yep, this is an issue with other languages too. Java, for instance, has no real notion of a constant either. Instead, you have statics, and as long as you put immutable objects in there then you're okay. Same workaround in Ruby, put an immutable object there instead of an array.

108 Name: #!/usr/bin/anonymous : 2007-12-01 05:01 ID:Heaven

>>107 Neither did Fortran:

       CALL FUN(41)
WRITE (10,1) 5
1 FORMAT (I5)
END
       SUBROUTINE FUN (I)
I = I + 1
END

109 Name: #!/usr/bin/anonymous : 2007-12-03 00:26 ID:7xvAlNy9

110

111 Name: #!/usr/bin/anonymous : 2008-01-05 02:25 ID:66OZq0xr

>>105

>So arguments to a block stomp all over variables in outer scopes.

Believe that's been fixed in 1.9.

Name: Link:
Leave these fields empty (spam trap):
More options...
Verification: