Too Many Ways?

C

Carlos

When I originally posted I thought someone would uncover the golden gun
of reasoning for '..' and '...', but it appears that there is none and
people accept things "as-is" for the sole purpose of them being "as-is".

Here is a reasoning:

Including:

0 3
****
-> 0..3

Excluding:

0 4
****
-> 0...4

0...10 : The 10 is outside the range. I want it inside: I move it to the
left: 0..10.

0..10 : The 10 is inside the range. I want it outside: I move it to the
right: 0...10.

Anyway, I don't think it needs any reasoning. It's only convention. In my
case, it didn't clash with any presumptions I had about what should they
mean.
 
M

Michael Gaunnac

Zach said:
I have been thinking lately about rangess and I will give you the two
different range operators and what comes to mind first.

Operator One: .. (two dots)
Example: 1..5
What comes to mind: Exclusive
How It Works: Inclusive (the last number 5, is included)

Operator Two: ... (three dots)
Example: 1...5
What comes to mind: Inclusive
How It Works: Exclusive (the last number 5, is excluded)

How range operators work seems sort of backwards to me. It would
appear as if the "..." (three dots) would be the inclusive one,
including the last value and the ".." (two dots) would be exclusive
and exclude the last value.

Am I off in my own little world here or have others pondered this?
(Perhaps it has been brought up before?) I am not requesting a change
on this, I am merely pointing out what seems unnatural in a very
natural language.

Zach

I don't know about potential parsing problems (or ugliness), but what about:


0..5 inclusive
0..<5 exclusive

Mike
 
T

trans. (T. Onoma)

I don't know about potential parsing problems (or ugliness), but what
about:


0..5 inclusive
0..<5 exclusive

Ha!

0><5
0><=5
0=><=5

Tie-fighter ranges :)

T.
 
J

John W. Kennedy

Bill said:
I agree. Having a separate operator for exclusive ranges just
complicates things. Why not just have '..' and the user can specify
either start..end or start..(end + 1) ?

You mean start..(end-1) -- but apart from that, because start...end is
faster than start..(end-1) unless the compiler recognizes start..(end-1)
as a special case and translates it to start...end.

And since programmers typically don't know whether the compiler does
that special case or not, many programmers will take the time to
"optimize" with endminus1=end-1 and then use start..endminus1.

All of which can be eliminated simply by having the exclusive range in
the first place.
 
M

Markus

Ha!

0><5
0><=5
0=><=5

Thank you! In my free time (ha!) I'm working on a patch to 1.8.2
to allow these sorts of operators (see
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/111627,
towards the end, and follow ups).

If I get it working, I'll try to implement this as an example.

-- Markus

P.S. My patch sort of works at present; bringing together a half dozen
threads on this list it'd call it alpheta, where:

alpheta = Proc.new:)===) { |s| (alpha...beta).member? s }
 
B

Bob Sidebotham

Zach said:
How range operators work seems sort of backwards to me. It would appear
as if the "..." (three dots) would be the inclusive one, including the
last value and the ".." (two dots) would be exclusive and exclude the
last value.

If you think of "..." as ellipsis (an English language punctuation
mark), then these definitions seem to match the Ruby interpretation:

http://www.yaelf.com/punctuation.shtml
Ellipsis: From the Greek for "to come short", originally used in geometry.

http://www.u-aizu.ac.jp/~tripp/punc.html
Ellipsis (...) is used to show that something has been omitted.

http://en.wikipedia.org/wiki/Ellipsis
an ellipsis (plural: ellipses) is a row of three dots (…) or asterisks
(* * *) indicating an intentional omission.

Bob Sidebotham
 
B

Bob Sidebotham

Brian said:
I second your example of:

def foo
...
yield stuff if block_given?
end

versus:

def foo(&blk)
...
blk.call(stuff) unless blk.nil?
end

I think I would have found iterators much easier to get my head round if I
had seen the second, rather than the first, initially.

And if you use the first syntax, there is the leap you have to make sooner
or later: "hang on - how do I pass this block to another method?"; or
conversely, "I have a proc object, but how do I pass it to a method which
expects a block?"

Well, yes, I got hung up on this right off the bat--I got it into my
head that yield was somehow dynamic, so that it would find some magic
block up the stack to yield to . So I was very puzzled when I couldn't
yield from within a method called by the method with the block.

I had assumed this level of dyanmicity since the block wasn't
specified--it seemed obvious to me that objects would be simply invoked
in a context where a yield would magically invoke some block defined
somewhere.

If I'd had to declare the block from the start, then this would never
have been an issue.

As a nuby, even when you *know* there's two ways to do something, you're
stuck wondering if there is some subtle reason that one is better (or
simply different) than the other. You see two mechanisms, can't find
anything written that describes when to use one or the other, and are
left puzzled and confused.

Bob
 
K

klaus schilling

Bob said:
As a nuby, even when you *know* there's two ways to do something, you're
stuck wondering if there is some subtle reason that one is better (or
simply different) than the other. You see two mechanisms, can't find
anything written that describes when to use one or the other, and are
left puzzled and confused.

that's what documentation is for.
there can never be two many ways as long as they are well described.

Klaus Schillling
 
G

Gavin Sinclair

Well, yes, I got hung up on this right off the bat--I got it into my
head that yield was somehow dynamic, so that it would find some magic
block up the stack to yield to . So I was very puzzled when I couldn't
yield from within a method called by the method with the block.
I had assumed this level of dyanmicity since the block wasn't
specified--it seemed obvious to me that objects would be simply invoked
in a context where a yield would magically invoke some block defined
somewhere.
If I'd had to declare the block from the start, then this would never
have been an issue.
As a nuby, even when you *know* there's two ways to do something, you're
stuck wondering if there is some subtle reason that one is better (or
simply different) than the other. You see two mechanisms, can't find
anything written that describes when to use one or the other, and are
left puzzled and confused.

Good point, Bob. I normally baulk at sensitivity to TMTOWTDI, but
this case registers clearly in my mind as a potential for confusion.
I guess from now on, when instructing someone in this area, I'll show
them both ways at the same time and deal with confusion upfront.

Stylistically, I tend to favour 'yield' when a block is optional, and
'block.call' when it's not.

def foo(a, b, &block)

visually suggests to me that a block is required. I usually mark a
method like this as well:

def foo(a, b) # :yield: peanut

I wonder what other people think/do?

Cheers,
Gavin
 
T

trans. (T. Onoma)

Good point, Bob. I normally baulk at sensitivity to TMTOWTDI, but
this case registers clearly in my mind as a potential for confusion.
I guess from now on, when instructing someone in this area, I'll show
them both ways at the same time and deal with confusion upfront.

Stylistically, I tend to favour 'yield' when a block is optional, and
'block.call' when it's not.

def foo(a, b, &block)

visually suggests to me that a block is required. I usually mark a
method like this as well:

def foo(a, b) # :yield: peanut

I wonder what other people think/do?

I never use yield. But I'll tell you what, I too think the above suggests that
&block is required. I really dislike inconsistency, so I even put in an RCR
about it, to the effect that

def foo(a, b, &block=nil)

would be needed to make the block optional. Matz said that it might be a good
idea, but would break code.

But since then, I've completely flipped positions, and more in concert with
the ideas of duck-typing, I wish all arguments worked like &block. e.g.

def foo(a, b)
"#{a}#{b}"
end

foo(1) #=> "1"

Just as block is nil in the above, so would b here. Likewise:

foo(1,2,3) #=> "12"

and 3 would be ignored.

Among other things this would reduce the need for (instantiating a method
object and) checking arity.

T.
 
B

Brian Candler

But since then, I've completely flipped positions, and more in concert with
the ideas of duck-typing, I wish all arguments worked like &block. e.g.

def foo(a, b)
"#{a}#{b}"
end

foo(1) #=> "1"

Just as block is nil in the above, so would b here. Likewise:

foo(1,2,3) #=> "12"

and 3 would be ignored.

Those semantics would be more consistent with yield and blk.call, and
generally I applaud anything which improves consistency:

def foo
yield 1
end

foo {|a,b| p a,b} # prints 1, nil

def bar(&blk)
blk.call(1)
end

bar {|a,b| p a,b} # prints 1, nil

However I tend towards the other position: that both the above examples
should raise an "incorrect number of arguments" exception. It tends to trap
errors sooner, rather than later. But I don't feel too strongly about which
way it's done; I feel much more strongly that it should be consistent!

I quite like your suggestion of &blk=nil though. At the moment in Ruby the
presence/absence of a block makes no difference, until you try to 'yield'
when no block is given. In particular, you can pass a block to a method
which doesn't expect one; I've been caught by that before!

So in an ideal world I'd have

def foo(a,b) # block forbidden, raise exception if you pass one
def foo(a,b,&c) # block required, raise exception if it's missing
def foo(a,b,&c=nil) # block optional

That syntax would also allow a 'default block':

def foo(a,b,&c=proc{|x| $stderr.puts x})

Regards,

Brian.
 
T

trans. (T. Onoma)

Those semantics would be more consistent with yield and blk.call, and
generally I applaud anything which improves consistency:

def foo
yield 1
end

foo {|a,b| p a,b} # prints 1, nil

def bar(&blk)
blk.call(1)
end

bar {|a,b| p a,b} # prints 1, nil

Great point!
However I tend towards the other position: that both the above examples
should raise an "incorrect number of arguments" exception. It tends to trap
errors sooner, rather than later. But I don't feel too strongly about which
way it's done; I feel much more strongly that it should be consistent!

Agreed. The reason I've gone the other way is b/c of this kind of thing:

def ducky_method(a,b,c,d,e)
end

...

case method:)ducky_method).arity
when 5
when 4
when 3
...

What I mean by ducky_method, is one I'm not sure about how it will be defined,
as is sometimes the case, especially with singleton methods. It may not be
very common, but, in fact, I just did this the other day.
I quite like your suggestion of &blk=nil though. At the moment in Ruby the
presence/absence of a block makes no difference, until you try to 'yield'
when no block is given. In particular, you can pass a block to a method
which doesn't expect one; I've been caught by that before!

Yes, I like it _better_ too. But like matz said it would break a good bit of
code :(

Fortunately going the other direction to achieve consistency doesn't. Albeit,
it does lead to "less obvious" errors at times, like you said, but that's an
acceptable trade-off to me --in fact it's part of why I choose Ruby over
other langs, i.e. duck-typing.
So in an ideal world I'd have

def foo(a,b) # block forbidden, raise exception if you pass one
def foo(a,b,&c) # block required, raise exception if it's missing
def foo(a,b,&c=nil) # block optional

That syntax would also allow a 'default block':

def foo(a,b,&c=proc{|x| $stderr.puts x})

This last should be allowed anyway, me thinks too.

T.
 
G

Gavin Sinclair

This last should be allowed anyway, me thinks too.

That's bad style, IMO. I'd prefer to read this:

def foo(a, b, &c=nil)
c ||= lambda { |x| ... }
...
end

Gavin
 
B

Brian Candler

Agreed. The reason I've gone the other way is b/c of this kind of thing:

def ducky_method(a,b,c,d,e)
end

...

case method:)ducky_method).arity
when 5
when 4
when 3
...

Ugh... fortunately I've never had to write code like that :) Especially
with the nasty overloading of negative values: -3 means 2..infinity. (Or is
it 4..infinity? I have to think about it every time. Remember, -1 means 0 to
infinity, obviously. So -3 is 2 to infinity. Got that?)

Anyway, I would hope that those methods would be declared as

def ducky_method(a,b,c)
def ducky_method(a,b,c,d=99)
def ducky_method(a,b,c,d=99,e=123)

and that you would therefore know that a call with 3 parameters makes sense.
Otherwise, perhaps those methods should have completely different names, if
they do different things altogether (i.e. if it has two legs it's a goose,
if it has 4 legs it's a mongoose)
Yes, I like it _better_ too. But like matz said it would break a good bit of
code :(

Perhaps these things are up for grabs in 2.0 though.

Regards,

Brian.
 
B

Brian Candler

Ugh... fortunately I've never had to write code like that :) Especially
with the nasty overloading of negative values: -3 means 2..infinity. (Or is
it 4..infinity? I have to think about it every time. Remember, -1 means 0 to
infinity, obviously. So -3 is 2 to infinity. Got that?)

Sorry, I will correct myself. -3 means at least 2, but the maximum limit is
not specified, and might be as low as 3.

def foo(a,b,c=nil)
end
method:)foo).arity # => -3

def bar(a,b,c=nil,d=nil,e=nil)
end
method:)bar).arity # => -3

It would be a nice use for ranges with infinite bounds, if they existed.

Regards,

Brian.
 
T

trans. (T. Onoma)

Sorry, I will correct myself. -3 means at least 2, but the maximum limit is
not specified, and might be as low as 3.

def foo(a,b,c=nil)
end
method:)foo).arity # => -3

def bar(a,b,c=nil,d=nil,e=nil)
end
method:)bar).arity # => -3

It would be a nice use for ranges with infinite bounds, if they existed.

Exactly what I was thinking.

T.
 
D

David A. Black

Hi --

Ugh... fortunately I've never had to write code like that :) Especially
with the nasty overloading of negative values: -3 means 2..infinity. (Or is
it 4..infinity? I have to think about it every time. Remember, -1 means 0 to
infinity, obviously. So -3 is 2 to infinity. Got that?)

Anyway, I would hope that those methods would be declared as

def ducky_method(a,b,c)
def ducky_method(a,b,c,d=99)
def ducky_method(a,b,c,d=99,e=123)

and that you would therefore know that a call with 3 parameters makes sense.
Otherwise, perhaps those methods should have completely different names, if
they do different things altogether (i.e. if it has two legs it's a goose,
if it has 4 legs it's a mongoose)

I have to admit (and my lack of C++, Java, etc. is probably showing
through here -- i.e., I'm perfectly willing to believe there's
something fundamental I'm just missing), I have never understood the
idea of differentiating methods by number of arguments. To me it's
sort of like differentiating them based on what letters the argument
names begin with. What if two slightly different forms of the method
both, by chance, took three arguments? Would one add a dummy
argument? The whole thing has always seemed to me like a
gimmick/workaround for languages with design problems.

(Again, all based on gut reaction and probably not enough
education....)


David
 
T

trans. (T. Onoma)

That's bad style, IMO. I'd prefer to read this:

def foo(a, b, &c=nil)
c ||= lambda { |x| ... }
...
end

Better:

c = lambda { |x| ... }
def foo(a, b, &c=c)
...
end

Why? Well, there seems to be a general aversion to lambdas in method
arguments. Probably b/c a) the Kernel#proc method is needed and b)
increased bracketing, i.e. cases like:

def foo(a, b, &c=proc{|x|
$stderr.puts x
# and more
# to do
}, &d=proc{|x|
$stdout.puts x
# and maybe
# another
})

But following your preference to its logical conclusion:

def foo(a=nil, b=nil, &c=nil)
a ||= "foo"
b ||= "bar"
c ||= lambda { |x| ... }
...
end

T.
 
G

Gavin Sinclair

But following your preference to its logical conclusion:
def foo(a=nil, b=nil, &c=nil)
a ||= "foo"
b ||= "bar"
c ||= lambda { |x| ... }
...
end

In that case:

def foo(a="foo", b="bar", &c=nil)
c ||= lambda { |x| ... }
...
end

I've no problem with default values appearing in the method signature,
but overly long default values, in my style, get relegated to the method
body.

Gavin
 
B

Brian Candler

Exactly what I was thinking.

I can't see what's hard about implementing them: have a couple of constant
objects for positive and negative infinity (like 'true' and 'false').

Then define -@ method so that -INF == NEGINF, and away you go.

I'd be quite happy to have this just for ranges, although I expect everyone
would want them to integrate with numbers: 3 * INF == INF and so on.

Or can we use the Float 'Infinity' for this?

Regards,

Brian.
 

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
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top