Too Many Ways?

T

trans. (T. Onoma)

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....)

My use case is this. I wrote an event-based AOP interface on top of
set_trace_func. To define advice, one defines "hook" methods. Now, I could
force the user to always define those methods with the full array of
arguments:

def my_advice(target, binding, back_binding)

Or I can allow them to use none, the first, the first and second, or all
three, as they require. i.e.

def my_advice
def my_advice(target)
def my_advice(target, binding)
def my_advice(target, binding, back_binding)

I choose to allow the user this flexibility. Thank goodness there are only
three!

T.
 
A

Ara.T.Howard

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?

no - because you would also have a different signature (else the methods
wouldn't be 'slightly different' (probably) :

//
// move from src to preallocated dest - leaving src untouched
//
int move (const char * const src, const char * dest);

//
// move from src to dest - creating dest and freeing src
//
int move (char * src, char * dest);

the idea is this - the compiler can tell you two things about your methods at
compile time : the types of the arguments and the number of them. it can use
this infomation to generate a case statement for you - eg it can know which
one to call freeing you from writing code like

case argv.size
when 2
arg0, arg1 = argv
case arg0
when String
...
end
when 3
...
...
end

you get the picture. i think it's more of an artifact of "this is everything
the compiler can possible know and we make this functionality availible for
you since it's there anyhow" rather than "we've implemented this handy feature
through blood sweat and tears". add to this the fact that variadic functions
are quite painful to write in a platform neutral way in c or c--.
The whole thing has always seemed to me like a gimmick/workaround for
languages with design problems.

i think it's more the compiler than the language itself. bascially the only
other way to do it would be so say - i'll just pass you everyting on the stack
and you can do what you want with it (variadic functions). the problem there
is that, due to lack of rtti, you cannot also know the types of you arguments
(witness format strings in printf) and so you must completely disgard type
safety and simply blind cast all args to their types. this will core dump, of
course, if done incorrectly. i'm not saying it can't be done in a compiled
lang. (take ocaml for example - which boxes vars similarly to ruby) - just
that the problem is, to a large degree, stemming from the compiler design and
not the lang design itself. for example, a compiler could box all vars and
make this info availible to the runtime - in fact, i think there is a 'typeof'
macro (or maybe this is the compile time one - if forget) for gnu c-- that
does tell you runtime type. (if this were about ruby i'd look it up and paste
some example code but i care so little about knowing any more c-- i couldn't
even be bothered).
(Again, all based on gut reaction and probably not enough education....)

i really don't think you want to get 'educated' on this topic. ;-)

kind regards.

-a
--
===============================================================================
| EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
| PHONE :: 303.497.6469
| A flower falls, even though we love it;
| and a weed grows, even though we do not love it.
| --Dogen
===============================================================================
 
D

David A. Black

Hi --

no - because you would also have a different signature (else the methods
wouldn't be 'slightly different' (probably) :

//
// move from src to preallocated dest - leaving src untouched
//
int move (const char * const src, const char * dest);

//
// move from src to dest - creating dest and freeing src
//
int move (char * src, char * dest);

the idea is this - the compiler can tell you two things about your methods at
compile time : the types of the arguments and the number of them.

[snip rest of very informative summary]

OK, the examples with compile-time type checking explain why it might
exist in other languages, and also why it continues to feel like a bad
fit for Ruby.
it can use
this infomation to generate a case statement for you - eg it can know which
one to call freeing you from writing code like

case argv.size
when 2
arg0, arg1 = argv
case arg0
when String
...
end
when 3
...
...
end

I hope I'm already free of that, so that's another reason why I
haven't felt the need for this :)

Thanks --


David
 
T

trans. (T. Onoma)

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.

Will you give it a go?
Or can we use the Float 'Infinity' for this?

Hmm... lets see...

irb(main):018:0> i = 10.0/0
=> Infinity
irb(main):019:0> r = 0..i
=> 0..Infinity
irb(main):020:0> r.member?(3)

And she hangs :(

I think it could work, but she needs some tuning. The idea that infinity is a
Float is a bit odd too. Probably should be it's own class. Any one see
otherwise?

Also, has anyone already gone to the trouble of creating an Infinity class?

T.
 
F

Francis Hwang

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

Klaus Schillling

Hear, hear. Whenever you have to learn a large subject, you're not
going to learn it all at once anyway. The question for the educator
is: Where is the student starting, and what's the best path to take
them into the material?

There's been a lot of talk about newbies in this thread but I don't
think there's any one kind of newbie; there are many. There are people
who have never programmed in a dynamic language, like the Java
refugees. There are people who are coming from a more design-centric
past, maybe they know Javascript and Flash's ActionScript, and are
looking for something meatier. There are the folks who come from other
languages, such as Smalltalk or Lisp or Haskell, that might make them
able to absorb some of the heavy concepts behind Ruby and maybe they
just need a crash course in the syntax.

The point is, you can't communicate with just a vague general idea of
a person. You have to target your message for a targeted audience. A
lot of documentation (Ruby and non-Ruby) doesn't do that.

F.
 
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

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!

matz, I'm curious. How do you feel about this?

T.
 
B

Bob Gustafson

How can there be a 'New' Programming Ruby?

I have the original edition here. I think it is perfect.

BobG
 
B

Brian Candler

Hmm... lets see...

irb(main):018:0> i = 10.0/0
=> Infinity
irb(main):019:0> r = 0..i
=> 0..Infinity
irb(main):020:0> r.member?(3)

And she hangs :(

irb(main):004:0> i=1.0/0
=> Infinity
irb(main):005:0> r=0..i
=> 0..Infinity
irb(main):006:0> r.include?(3)
=> true
irb(main):007:0> r.member?(3)
<< hangs >>
^CIRB::Abort: abort then interrupt!!
from /usr/local/lib/ruby/1.8/irb.rb:81:in `irb_abort'
from /usr/local/lib/ruby/1.8/irb.rb:241:in `signal_handle'
from /usr/local/lib/ruby/1.8/irb.rb:66:in `start'
from /usr/local/lib/ruby/1.8/irb.rb:65:in `call'
from (irb):7:in `member?'
from (irb):7

According to PickAxe v1, both include? and member? come from Enumerable, and
are just aliases for each other. Clearly this is not the case in 1.8.x any
more.

Looking at the source, I see that Range#member? iterates using the 'each'
function, whereas Range#include? just compares against the start and end
values.

I think it's a bug that Range#member? doesn't do a 'break' when it finds a
match. OTOH, I guess it's pretty dubious to be relying on Enumerable-type
methods over an infinite enumeration :)

Anyway, on to a quick implementation. 'Infinity' printed by Float#to_s is
not actually available as a constant:

irb(main):002:0> 1.0/0
=> Infinity
irb(main):003:0> Infinity
NameError: uninitialized constant Infinity
from (irb):3

but I'm going to break normal class encapsulation by defining it as one at
the top level anyway, instead of as Range::Infinity. Simple listing attached
below.
I think it could work, but she needs some tuning. The idea that infinity is a
Float is a bit odd too. Probably should be it's own class. Any one see
otherwise?

It just needs to be any unique sentinel value (or pair of values, for
Infinity and -Infinity). I thought initially that it should be a class, or
two different instances of a class (one instance for +Inf, one for -Inf).
But then again, since the Float infinity has the properties we need, why not
just re-use it? Plus it has useful Comparable properties.

irb(main):001:0> i = 1.0/0
=> Infinity
irb(main):002:0> 3 < i
=> true
irb(main):003:0> 3 > i
=> false
irb(main):004:0> 3 < -i
=> false
irb(main):005:0> 3 > -i
=> true

Actually, this means that Range with infinite limits works already, without
any extra code! But since not everything is necessarily directly comparable
to a Float infinity

If this went into the core, there would be some syntactic sugar which could
be done: e.g. (1..) becomes a synonym for (1..Infinity)

Regards,

Brian.

----- 8< --------------------------------------------------------------
Infinity = 1.0/0

# The test cases below pass WITHOUT adding this extra code! But it
# would be useful for ranges whose elements are not directly comparable
# to Infinity using <, >, <= etc.
class Range
def include?(val)
if first == -Infinity
if last == Infinity
true
elsif exclude_end?
val < last
else
val <= last
end
elsif last == Infinity
val >= first
else
super
end
end
end

if __FILE__ == $0
require 'test/unit'
class InfTest < Test::Unit::TestCase
def testme
a = (-Infinity..-3)
assert_equal(true, a.include?(-Infinity))
assert_equal(true, a.include?(-4))
assert_equal(true, a.include?(-3))
assert_equal(false, a.include?(-2))
assert_equal(false, a.include?(Infinity))
a = (-Infinity...-3)
assert_equal(true, a.include?(-Infinity))
assert_equal(true, a.include?(-4))
assert_equal(false, a.include?(-3))
assert_equal(false, a.include?(-2))
assert_equal(false, a.include?(Infinity))
a = (-3..Infinity)
assert_equal(false, a.include?(-Infinity))
assert_equal(false, a.include?(-4))
assert_equal(true, a.include?(-3))
assert_equal(true, a.include?(-2))
assert_equal(true, a.include?(Infinity))
a = (-Infinity..Infinity)
assert_equal(true, a.include?(-Infinity))
assert_equal(true, a.include?(-4))
assert_equal(true, a.include?(-3))
assert_equal(true, a.include?(-2))
assert_equal(true, a.include?(Infinity))
a = (-3..-2)
assert_equal(false, a.include?(-Infinity))
assert_equal(false, a.include?(-4))
assert_equal(true, a.include?(-3))
assert_equal(true, a.include?(-2))
assert_equal(false, a.include?(Infinity))
end
end
end
 
B

Brian Candler

Actually, this means that Range with infinite limits works already, without
any extra code! But since not everything is necessarily directly comparable
to a Float infinity

...then it may be worth testing the boundary points explicitly against
-Infinity and Infinity (was what I meant to write)
 
S

Stephan Kämper

Bob said:
How can there be a 'New' Programming Ruby?

Let some Ruby enthusiasts do some incedible work for a while. ;-)

The new PickAxe is about Ruby 1.8 (with differences to 1.6 being
marked). It deals with the standard library which has grown a lot since
1.6. It contains a chapter about test/unit and some more stuff that
hasn't been discussed in the 1st edition - stuff that didn't even exist
at the time the 1st edition was first published.
I have the original edition here. I think it is perfect.

It is... Well, it was until last friday (when I got my PickAxe-PDF). :)

Happy rubying

Stephan
 
T

trans. (T. Onoma)

Nice Brian!

According to PickAxe v1, both include? and member? come from Enumerable,
and are just aliases for each other. Clearly this is not the case in 1.8.x
any more.

Looking at the source, I see that Range#member? iterates using the 'each'
function, whereas Range#include? just compares against the start and end
values.

I think it's a bug that Range#member? doesn't do a 'break' when it finds a
match. OTOH, I guess it's pretty dubious to be relying on Enumerable-type
methods over an infinite enumeration :)

I see! Well, yes it is a bug (IMHO). Moreover #include? should be the same as
#member? According to this, what #include? is doing now it really something
different: #between?

irb(main):002:0> r = (0...10)
=> 0...10
irb(main):003:0> r.include?(4.3)
=> true

Which isn't really right. Of course the problem is per my last post.
Anyway, on to a quick implementation. 'Infinity' printed by Float#to_s is
not actually available as a constant:

irb(main):002:0> 1.0/0
=> Infinity
irb(main):003:0> Infinity
NameError: uninitialized constant Infinity
from (irb):3

but I'm going to break normal class encapsulation by defining it as one at
the top level anyway, instead of as Range::Infinity. Simple listing
attached below.

Works for me.
[snip]

If this went into the core, there would be some syntactic sugar which could
be done: e.g. (1..) becomes a synonym for (1..Infinity)

At the very least I'll add it my library. I have quite an extensive
collection, that I may realease one day.

I'll look at it some more later. Do you see anything in particular that's
problematic with it?

T.
 
B

Brian Candler

I see! Well, yes it is a bug (IMHO). Moreover #include? should be the same as
#member? According to this, what #include? is doing now it really something
different: #between?

irb(main):002:0> r = (0...10)
=> 0...10
irb(main):003:0> r.include?(4.3)
=> true

Which isn't really right.

Errm, well it looks right to me. If you're saying that the range 0...10 is
actually the set of integers 0,1,2,3,4,5,6,7,8,9, then 4.3 is not a member
of that set; but if you consider it as a continuous range along the number
line, then 4.3 is included within that range. And Ruby does indeed recognise
the distinction:

irb(main):001:0> r = (0...10)
=> 0...10
irb(main):002:0> r.include?(4.3)
=> true
irb(main):003:0> r.member?(4.3)
=> false
I'll look at it some more later. Do you see anything in particular that's
problematic with it?

I think it will work fine. The problem will be that many more cases will
turn up where Infinity should be treated as a special case, for consistency.

a[2,Infinity]
a[2..Infinity] # currently gives RangeError: float Inf out of range of integer
... probably lots of others

And unfortunately we still don't eliminate the smelly ranges where last <
first.

a = "mystring"
a[2..-1]

Regards,

Brian.
 
T

trans. (T. Onoma)

Errm, well it looks right to me. If you're saying that the range 0...10 is
actually the set of integers 0,1,2,3,4,5,6,7,8,9, then 4.3 is not a member
of that set; but if you consider it as a continuous range along the number
line, then 4.3 is included within that range. And Ruby does indeed
recognise the distinction:

Well, yes, one could look at it that way. But I mean in accordance to the
definition as given by Enumerable. From the Pickaxe:

include?

enumObj.include?( anObject ) -> true or false

Returns true if any member of enumObj equals anObject. Equality
is tested using ==.

-T.
 
B

Brian Candler

Well, yes, one could look at it that way. But I mean in accordance to the
definition as given by Enumerable. From the Pickaxe:

include?

enumObj.include?( anObject ) -> true or false

Returns true if any member of enumObj equals anObject. Equality
is tested using ==.

It depends whose definition you think is "correct". Enumerable has a method,
but Range overrides it. It's not documented as such in Enumerable, but maybe
it's intended that 'include?' and 'member?' behave differently where the
object permits it, and only the default lowest-common-denominator case
results in them being the same. Poor documentation is annoying though.

I suppose it could be regularised by:
- removing Enumerable#include? entirely; or
- having a new method for Range (e.g. Range#within_bounds?) which leaves
include? and member? behaving the same as Enumerable

But from a duck-typing point of view, it might make sense to be able to send
foo.include?(bar) and have the 'optimised' behavior where an object supports
it, falling back to the Enumerable behaviour where not.

It's another example where having two method names for the same thing is
confusing; in this case doubly so because they are documented in one place
as being aliases, but elsewhere they are made to do different things.

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,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top