x.f! RCR

G

Greg McIntyre

It bugs me that some methods have a ! on the end and some don't. It
seems very arbitrary and inconsistent to me. And the same for ?. I'd
like it if ! was a special keyword or operator or something, so that

x.f!

was expanded to this

(x = x.f)

if x#f! was not already defined (so that in-place algorithms retained
their efficiency). Has this already been suggested?
 
G

Greg McIntyre

After thinking more about this, I can see why certain methods have a !
version available and others don't. Or I think I do. It's about data
encapsulation, to some extent. If I have

class Foo
attr :bar

def initialize
@bar = [1,2,3]
end
end
foo = Foo.new

I want to allow

foo.bar.map!{|x| x*2 }

Because it's convenient and it seems legitimate. But I don't want things
which will change@bar's class. Like

foo.bar.join!(', ')

would be bad because then @bar would be something completely different
all of a sudden and the poor Foo object and its clients might not like
that. More generally, I guess this is about repeatable methods. There's
a word for that, I've forgotten. i.e. You can do this:

foo.bar.map!{|x| x*2 }.map!{|x| x*2 }

But you can't do this:

foo.bar.join!(', ').join!(', ')

Hence join! is not defined.

Am I on the right track?
 
M

Maik Schmidt

Greg said:
It bugs me that some methods have a ! on the end and some don't. It
seems very arbitrary and inconsistent to me. And the same for ?.

Hmm, maybe the whole thing is much simpler than you think:

From matz' "Ruby in a nutshell":

<quote>
You can append ! or ? to the name of a Ruby method. Traditionally,
! is appended to a method that requires more caution than the variant
of the same name without !. A question mark ? is appended to a method
that determines the state of a Boolean value, true or false.
</quote>

That's all.

Cheers,

<maik/>
 
G

Greg McIntyre

Maik Schmidt said:
Hmm, maybe the whole thing is much simpler than you think:

From matz' "Ruby in a nutshell":

<quote>
You can append ! or ? to the name of a Ruby method. Traditionally,
! is appended to a method that requires more caution than the variant
of the same name without !. A question mark ? is appended to a method
that determines the state of a Boolean value, true or false.
</quote>

Yes, but it wasn't until about 15 minutes ago that I understood why, in
the standard library, some methods have ! and some don't. That is, what
the actual rationale is. I'm not talking about capability, I'm talking
about actual practice. Until 15 mintues ago, it just seemed like
inconsistent use. And there is still some inconsistency. I think
Array#delete_if should be Array#delete_if!, for example.

I think it's a subtle issue. Too subtle. If it took me months of being a
Ruby enthusiast to pay enough attention to figure it out, I think it's
probably a bit too tricky... but maybe it's just me being stupid. ?:)
 
M

Maik Schmidt

I think it's a subtle issue. Too subtle. If it took me months of being a
Ruby enthusiast to pay enough attention to figure it out, I think it's
probably a bit too tricky... but maybe it's just me being stupid. ?:)

You are absolutely right. My point is much simpler: It is nearly
impossible to enforce the behaviour you expect. Currently, developers
use the ! and ? in arbitrary ways and often they simply forget to use it
(it's the same with 'const' in C++, e.g., it is a really good feature,
but many developers simply forget to use it as often as possible). So,
even if the simple convention to use ! and ? in method names would be
replaced by any other syntactic construct, the problem would remain the
same, but IMHO you would have added another piece of syntactic sugar to
the Ruby language that would be of at least questionable benefit.

Cheers,

<maik/>
 
M

Michael Neumann

Greg said:
Yes, but it wasn't until about 15 minutes ago that I understood why, in
the standard library, some methods have ! and some don't. That is, what
the actual rationale is. I'm not talking about capability, I'm talking
about actual practice. Until 15 mintues ago, it just seemed like
inconsistent use. And there is still some inconsistency. I think
Array#delete_if should be Array#delete_if!, for example.

My understanding is, that "delete" is a destructive word itself, so it doesn't
need to be marked explicitly as "dangerous". If you need a non-destructive
delete_if version, better use the Enumerable#reject method, which IMHO
describes better it's internal sematics. Here, it's destructive counterpart,
reject! exists.

Your suggestion is that delete_if does not delete anything (it creates a new
array), where delete_if! would. Principle of Least Surprise? Not for me!


Regards,

Michael
 
D

Dan Doel

Michael said:
Your suggestion is that delete_if does not delete anything (it creates a new
array), where delete_if! would. Principle of Least Surprise? Not for me!

That depends on your background, I suppose. If I'm not mistaken, ! and
? probably come from Scheme,
where ! is usually used to denote that it causes a side effect on the
arguments. Since Scheme advocates
functional programming,

(delete_if predicate list)

would return a new list with the proper elements deleted, while

(delete_if! predicate list)

would delete the elements in place (probably returning the list as well).

If you're writing in a functional manner, it's not at all surprising to
expect a delete... or remove... function to
not modify the initial list (in fact it's almost a requirement if you
want to prove certain correctness
requirements for a program). In that case, the fact that there is no
#delete_if that doesn't modify the base
list would be an inconvenience. Not that Ruby's lists are exactly
suited for functional programming, but
food for thought.

Cheers.

- Dan
 
M

Michael Neumann

That depends on your background, I suppose. If I'm not mistaken, ! and
? probably come from Scheme,

I've to admit, I never really used Scheme or Lisp.
where ! is usually used to denote that it causes a side effect on the
arguments. Since Scheme advocates
functional programming,

(delete_if predicate list)

would return a new list with the proper elements deleted, while

(delete_if! predicate list)

would delete the elements in place (probably returning the list as well).

If you're writing in a functional manner, it's not at all surprising to
expect a delete... or remove... function to
not modify the initial list (in fact it's almost a requirement if you
want to prove certain correctness
requirements for a program). In that case, the fact that there is no
#delete_if that doesn't modify the base
list would be an inconvenience. Not that Ruby's lists are exactly
suited for functional programming, but
food for thought.

Thanks for the insights into Scheme. Scheme is probably much more
consistent than Ruby. Ruby instead favours 'matz' POLS over consistence
(but it's not that inconsistent at all :).

Regards,

Michael
 
G

Greg McIntyre

Michael Neumann said:
My understanding is, that "delete" is a destructive word itself, so it
doesn't need to be marked explicitly as "dangerous". If you need a
non-destructive delete_if version, better use the Enumerable#reject
method, which IMHO describes better it's internal sematics. Here, it's
destructive counterpart, reject! exists.

Your suggestion is that delete_if does not delete anything (it creates
a new array), where delete_if! would. Principle of Least Surprise? Not
for me!

That isn't my suggestion, but we do have conflicting POLS. :)

My suggestion is that one _should_ be able to do this:

a = [1,2,3]
a.join!

with only Array#join defined.

Basically I see inconsistency. Personally I'd expect delete_if to have
a ! because it modifies the receiver like map! and reject!, regardless
of whether it's clear. Clarity is not just subjective, it's highly
subjective! :)

So _is_ there a rule or mnemonic for ! usage in method names, other than
"danger"? Is there a technical rule? In the standard library it seems to
be that ! is used for in-place transformations to the same class; is
that it?
 
B

Brett H. Williams

On Nov 20, Greg McIntyre wrote:
[snip]
My suggestion is that one _should_ be able to do this:

a = [1,2,3]
a.join!

with only Array#join defined.

What would this do? Something like a = a.join ?

irb(main):001:0> a = [1,2,3] # => [1, 2, 3]
irb(main):002:0> a.join! # => "123"
irb(main):003:0> a # => "123"

??

The above is doing a variable assignment with a method. So the receiver is
a different object after the method is called.

This sounds undesirable. I think of ! as either meaning danger OR operate
on an existing object, but not this.

I'm not sure this is even possible in Ruby, but I know it violates my POLS
:|
 
G

Greg McIntyre

Brett H. Williams said:
The above is doing a variable assignment with a method. So the
receiver is a different object after the method is called.

That's true...

So that's 1 reason against it so far. However ! does mean danger... :)

This sounds undesirable. I think of ! as either meaning danger OR
operate on an existing object, but not this.

I'm not sure this is even possible in Ruby, but I know it violates my
POLS:|

I must have a non-standard POLS. :) I can see this trend continuing, so
I'll happily stand down my idea. I don't like treading on other people's
POLS. I think really I just needed to understand the reasons behind !.
There's a long road between code being implemented and code being used
and sometimes the rationales get lost along the way.
 
C

Charles Hixson

Brett said:
On Nov 20, Greg McIntyre wrote:
...

What would this do? Something like a = a.join ?

irb(main):001:0> a = [1,2,3] # => [1, 2, 3]
irb(main):002:0> a.join! # => "123"
irb(main):003:0> a # => "123"

??

The above is doing a variable assignment with a method. So the receiver is
a different object after the method is called.

This sounds undesirable. I think of ! as either meaning danger OR operate
on an existing object, but not this.

I'm not sure this is even possible in Ruby, but I know it violates my POLS
:|
To me it seems worse than that. You've been assuming that the memebers
ot the list were integers. But they could well be floats, or procs.
floats would give you very peculiar results, whereas procs... (shudder!)
 
M

Mark Wilson

On Nov 19, 2003, at 12:43 PM, Dan Doel wrote:
[snip]
That depends on your background, I suppose. If I'm not mistaken, !
and ? probably come from Scheme,
where ! is usually used to denote that it causes a side effect on the
arguments.

[snip]

My understanding of the use of ! in Scheme (and Lisp) is that ! is used
for functions that serve as assignments (in the sense of creating state
in the environment). In Ruby, by a mostly perfect analogy, ! is
appended to methods that modify in place the value of an object, where
the use of ! would not be duplicative (i.e., delete! would be
duplicative). Frequently, in Ruby there will be two methods that do the
same thing (such as slice and slice!) except that one modifies the
object in place.

The main point of ? and ! is solely directed to enhancing the ability
of people to understand a program they are reading. In light of this,
any actual or supposed inconsistencies should be raised and actual
inconsistencies should be eliminated. I think the core library is
consistent now.

It is easier to determine when to use ? than it is to decide that a
particular method needs an appended !. In general, I think it is best
to use ! to differentiate between two versions of what is,
functionally, a single method and to not use ! where state is not
changed or where only a modify in place method is appropriate.

Regards,

Mark
 
G

Greg McIntyre

Charles Hixson said:
To me it seems worse than that. You've been assuming that the
memebers ot the list were integers. But they could well be floats, or
procs. floats would give you very peculiar results, whereas procs...
(shudder!)

I'm really not sure why you're shuddering. It's no different than doing
this:

a = [proc{}, proc{}]
# => [#<Proc:0x40205390@(irb):1>, #<Proc:0x40205368@(irb):1>]

a = a.join(', ')
# => "#<Proc:0x40205390@(irb):1>, #<Proc:0x40205368@(irb):1>"

So it's no more or less desirable than doing that. It's like +=, it's
just shorthand. += doesn't always make sense either.
 
D

Dan Doel

Well, I agree. However, I would argue that there should be a #delete
and #delete! for that very reason.

I was merely arguing that #delete doesn't necessarily have to modify the
array in place you could have:

a = ["a", "b", "b", "c"]

b = a.delete "b"

p a # => ['a', 'b', 'b', 'c']
p b # => ['a', 'c']

And then #delete! would make sense as you describe.

As I glance at the documentation, there is no method that does this
currently, although it could be easily
done with #map and #compact.

In fact, all destructive methods could be done this way. Some would be
somewhat stupid, such as #clear
which would just return an empty array, which is fairly useless except
in the #clear! case. The downside of
the a.f! => a = a.f proposal is that sometimes the former can be
implemented much more efficiently than the
latter.

Oh, and when I said "side-effects" in my last mail, I actually meant
side-effects due to assignment. I realize
there are others such as reading and writing data, and I'm sure you're
right that Scheme doesn't use ! in those
cases. However, I believe there's a convention that if a function
modifies one of the arguments (that is, uses
(set!) with them), the function also includes an ! in the name.

Regards.

- Dan
 
G

Greg McIntyre

Dan Doel said:
In fact, all destructive methods could be done this way. Some would be
somewhat stupid, such as #clear
which would just return an empty array, which is fairly useless except
in the #clear! case. The downside of
the a.f! => a = a.f proposal is that sometimes the former can be
implemented much more efficiently than the
latter.

The proposal also states that the expansion is only performed if the
method f! is not defined. Hence f! can be defined the efficient way, if
such a way exists.
 
M

Michael Neumann

Well, I agree. However, I would argue that there should be a #delete
and #delete! for that very reason.

I was merely arguing that #delete doesn't necessarily have to modify the
array in place you could have:

a = ["a", "b", "b", "c"]

b = a.delete "b"

delete actually returns the removed element ("b" in this case) or nil if
no element to delete was found. It does not return the array.

a = ["a", "b", "b", "c"]

a.delete "b" # => "b"
a.delete "b" # => nil (no more "b" in a)
a.delete "x" # => nil (no "x" in a)

So it can't be modified for non-destructive deletion this way.

What you want in #reject (indeed #reject is more general than #delete,
as it's part of Enumerable, not just Array).

a = ["a", "b", "b", "c"]

b = a.reject {|e| e == "b"}

Here the #reject! method works as expected.

Regards,

Michael
 
D

Dan Doel

Oh, that's actually a very good point about ! methods. They're usually
defined to have
a return value that evaluates to true when the object was modified, and
evaluates to
false when not modified.

So f.b! => f = f.b doesn't work because it always returns true
(essentially) unless
f.b is false or nil for some reason.

I had forgotten about that. That effectively kills this proposal
assuming you want to
preserve existing semantics.

- Dan
 
G

Greg McIntyre

Dan Doel said:
Oh, that's actually a very good point about ! methods. They're usually
defined to have
a return value that evaluates to true when the object was modified,
and evaluates to
false when not modified.

So f.b! => f = f.b doesn't work because it always returns true
(essentially) unless
f.b is false or nil for some reason.

I had forgotten about that. That effectively kills this proposal
assuming you want to
preserve existing semantics.

Gosh, I had no idea! Well, at least I've learnt something new. :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,141
Messages
2,570,813
Members
47,357
Latest member
sitele8746

Latest Threads

Top