[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

The duck's backside

Tobias Weber

5/28/2008 7:41:00 AM

Hi,
as a newcomer I miss method overloading with named parameters and types
(Objective-C!). Mainly because my code is riddled with ugly "case
argument.class" and "raise unless Integer". But maybe that's the problem.

What is the Duck way to check arguments? I have to because I'm passing
on to a rather fragile web service.

I only need numbers (ids) and strings in my instance variables, but
considered making a class for each one to catch illegal data in
initialize.

--
Tobias Weber
76 Answers

Robert Klemme

5/28/2008 8:59:00 AM

0

2008/5/28 Tobias Weber <towb@gmx.net>:
> as a newcomer I miss method overloading with named parameters and types
> (Objective-C!). Mainly because my code is riddled with ugly "case
> argument.class" and "raise unless Integer". But maybe that's the problem.

You cannot expect to carry your Objective-C style of programming over
to Ruby. Instead, if you want to have overloading you need to either
use different method names (appropriate if the overloaded methods act
differently) or you can do it with a single method (e.g. if the
overloading was just there to adjust type - for example on version
accepts an int and another accepts a float and internally you continue
processing with a float).

> What is the Duck way to check arguments? I have to because I'm passing
> on to a rather fragile web service.

The duck way is to not check arguments.

> I only need numbers (ids) and strings in my instance variables, but
> considered making a class for each one to catch illegal data in
> initialize.

It's difficult to answer on such a general scale. Usually, if there
are particular requirements you have on arguments passed to methods
you should check inside the method that receives them. Note, that for
integers there is to_int so you can do:

class Foo
def doit length
@l = length.to_int
end
end

Can you give a more concrete example?

Kind regards

robert

--
use.inject do |as, often| as.you_can - without end

Tobias Weber

5/28/2008 9:56:00 AM

0

In article
<9e3fd2c80805280159y5bad0a73tbc4f845f90246580@mail.gmail.com>,
Robert Klemme <shortcutter@googlemail.com> wrote:

> differently) or you can do it with a single method (e.g. if the
> overloading was just there to adjust type - for example on version

And if the types are rather different ducks, would you "case type"?

> are particular requirements you have on arguments passed to methods
> you should check inside the method that receives them. Note, that for

Yes, but it's uuugly ;)

> def doit length
> @l = length.to_int
> end

Nice, I had forgotten about that one, and to_i won't do as even nil
answers to it.

> Can you give a more concrete example?

def doit(a, b, c = nil)
begin
if c.nil? and b > 0
c = "ignore"
elsif b.nil? and c > 0
uaid = "ignore"
else
raise
end
rescue RuntimeError, NoMethodError
raise ArgumentError, "need exactly one number"
end
@http.post("first=#{a}&second=#{b}&third=#{c}")
end

--
Tobias Weber

Eleanor McHugh

5/28/2008 12:48:00 PM

0

On 28 May 2008, at 11:01, Tobias Weber wrote:
> def doit(a, b, c = nil)
> begin
> if c.nil? and b > 0
> c = "ignore"
> elsif b.nil? and c > 0
> uaid = "ignore"
> else
> raise
> end
> rescue RuntimeError, NoMethodError
> raise ArgumentError, "need exactly one number"
> end
> @http.post("first=#{a}&second=#{b}&third=#{c}")
> end


This is off the top of my head so the usual caveats apply:

def doit a, b, c = nil
begin
case
when b && c
raise
when b && b.respond_to?(:to_int)
uaid = "ignore" if b.to_int > 0
when c && c.respond_to?(:to_int)
c = "ignore" if c.to_int > 0
else
raise
end
rescue
raise ArgumentError, "need exactly one number"
end
@post("first=#{a}&second=#{b.to_int}&third=#{c.to_int}")
end

This makes the exclusion of b and c more explicit, which will make it
much easier for a maintainer to understand what's going on. I've also
made integer conversion explicit and focused the filtering logic on
making sure it's appropriate as that's what you're really trying to
achieve, however this kind of belt-and-braces approach suggests that
you're tackling the problem from the wrong angle and should look at
the data-flow elsewhere in your code.

One possibility would be to rejig the whole thing as follows:

def doit a, v, param = :second
raise ArgumentError, "needs a numeric value" unless v.respond_to?
(:to_int)
v = "ignore" if (param == :third) && v > 0
@post("first=#{a}&second=#{v if param == :second}&third=#{v if param
== :third}")
end

and then evolve it from there, although it's also pretty damn ugly.

Where this is called you could write something along the lines of:

if b && c then
raise ArgumentError, "needs exactly one additional argument"
else
doit a, (b || c), (b.nil? ? :third : :second)
end

which could be further encapsulated as:

doit a, b, c = nil
raise ArgumentError, "needs exactly one additional argument" if b && c
v = b || c
raise ArgumentError, "need a numeric parameter" unless v.respond_to?
(:to_int)
param = b.nil? ? :third : :second
v = "ignore" if (param == :third) && v > 0
@post("first=#{a}&second=#{v if param == :second}&third=#{v if param
== :third}")
end


Ellie

Eleanor McHugh
Games With Brains
http://slides.games-with-...
----
raise ArgumentError unless @reality.responds_to? :reason



Robert Dober

5/28/2008 1:06:00 PM

0

On Wed, May 28, 2008 at 9:45 AM, Tobias Weber <towb@gmx.net> wrote:
>
The first idea would be to do something like the following

module MyChecker
MyConstraintError = Class::new RuntimeError

def make_ivar name, value, *allowedClasses
raise MyConstraintError, "...." unless
allowedClasses.any?{ |klass| klass === value }
instance_variable_set name, value
end
end

class MyClass
include MyChecker
def initialize int_val, str_val, val
make_ivar :@int, int_val, Integer
make_ivar :@str, str_val, String
make_ivar :@var, val, Integer, String
....

HTH
Robert


--
http://ruby-smalltalk.blo...

---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein

Tobias Weber

5/28/2008 1:15:00 PM

0

In article
<9560F017-58CA-48EB-BCC3-3B16758516C1@games-with-brains.com>,
Eleanor McHugh <eleanor@games-with-brains.com> wrote:

> > uaid = "ignore"

Ups, shoulda been c =

> @post("first=#{a}&second=#{b.to_int}&third=#{c.to_int}")

Won't work as the web service actually expects either a number or the
string "ignored"

> This makes the exclusion of b and c more explicit, which will make it

First I actually tried exclusive or, but that operates bit-wise on
Integer so the results were strange.

I've always been very comfortable with perl's boolean logic, so I have
to relearn some tricks.

(in Ruby 0 and "" are true)

> achieve, however this kind of belt-and-braces approach suggests that
> you're tackling the problem from the wrong angle and should look at
> the data-flow elsewhere in your code.

So that doit() is only passed legal values? That should be the case, but
I want to make extra sure not to give that foreign web service something
it can't swallow.

> def doit a, v, param = :second
> raise ArgumentError, "needs a numeric value" unless v.respond_to?
> (:to_int)
> v = "ignore" if (param == :third) && v > 0

That is essentially overloading doit() for first or second parameter,
and a good idea! So far I just wrapped the http calls in convenience
methods one by one.

--
Tobias Weber

Robert Klemme

5/28/2008 2:11:00 PM

0

On 28 Mai, 15:14, Tobias Weber <t...@gmx.net> wrote:
> In article
> <9560F017-58CA-48EB-BCC3-3B1675851...@games-with-brains.com>,
> Eleanor McHugh <elea...@games-with-brains.com> wrote:
>
> > > uaid = "ignore"
>
> Ups, shoulda been c =
>
> > @post("first=#{a}&second=#{b.to_int}&third=#{c.to_int}")
>
> Won't work as the web service actually expects either a number or the
> string "ignored"

You can do

@post("first=#{a}&second=#{b.to_int rescue 'ignored'}&third=#{c.to_int
rescue 'ignored'}")

Of course, defining a method for this would be better. How about:

def intify(x)
x.to_int rescue "ignored"
end

def doit(a, b, c = nil)
(b, c = intify(b), intify(c)).select {|x| Numeric === x}.size != 1
and
raise ArgumentError, "need exactly one number"
@http.post("first=#{a}&second=#{b}&third=#{c}")
end

Cheers

robert

Eleanor McHugh

5/28/2008 3:24:00 PM

0


On 28 May 2008, at 14:15, Tobias Weber wrote:

> In article
> <9560F017-58CA-48EB-BCC3-3B16758516C1@games-with-brains.com>,
> Eleanor McHugh <eleanor@games-with-brains.com> wrote:
>
>>> uaid = "ignore"
>
> Ups, shoulda been c =

Which makes things simpler ;)

doit a, b, c = nil
raise ArgumentError, "needs exactly one additional argument" if b && c
v = b || c
raise ArgumentError, "need a numeric parameter" unless v.respond_to?
(:to_int)
@post "first=#{a}&" + (b.nil? ? "second=ignored&third=#{v}" :
"second=#{v}&third=ignored")
end

> So that doit() is only passed legal values? That should be the case,
> but
> I want to make extra sure not to give that foreign web service
> something
> it can't swallow.
>
>> def doit a, v, param = :second
>> raise ArgumentError, "needs a numeric value" unless v.respond_to?
>> (:to_int)
>> v = "ignore" if (param == :third) && v > 0
>
> That is essentially overloading doit() for first or second parameter,
> and a good idea! So far I just wrapped the http calls in convenience
> methods one by one.

I'm not suggesting it's a good idea...


Ellie

Eleanor McHugh
Games With Brains
http://slides.games-with-...
----
raise ArgumentError unless @reality.responds_to? :reason



Mark Wilden

5/28/2008 5:58:00 PM

0


On May 28, 2008, at 8:24 AM, Eleanor McHugh wrote:

> doit a, b, c = nil
> raise ArgumentError, "needs exactly one additional argument" if b
> && c
> v = b || c
> raise ArgumentError, "need a numeric parameter" unless
> v.respond_to?(:to_int)
> @post "first=#{a}&" + (b.nil? ? "second=ignored&third=#{v}" :
> "second=#{v}&third=ignored")
> end

This looks like a great solution!

But if you want to check for numeric parameters, would it be better
just to use Numeric === v ? I know this isn't the ducky way, but it
certainly seems more "intentional."

///ark

Jim Menard

5/28/2008 6:13:00 PM

0

Tobias,

> as a newcomer I miss method overloading with named parameters and types
> (Objective-C!). Mainly because my code is riddled with ugly "case
> argument.class" and "raise unless Integer". But maybe that's the problem.
>
> What is the Duck way to check arguments? I have to because I'm passing
> on to a rather fragile web service.

Don't bother. Use your arguments, assuming they are the correct type.
If they are not, then a runtime exception will be thrown. Unless the
input to this function is coming from an untrusted source, the inputs
will always be the correct type. If they are not, then something is
seriously wrong.

Jim

>
> I only need numbers (ids) and strings in my instance variables, but
> considered making a class for each one to catch illegal data in
> initialize.
>
> --
> Tobias Weber
>
>



--
Jim Menard, jimm@io.com, jim.menard@gmail.com
http://www.io....

Eleanor McHugh

5/28/2008 6:45:00 PM

0

On 28 May 2008, at 18:57, Mark Wilden wrote:
> On May 28, 2008, at 8:24 AM, Eleanor McHugh wrote:
>> doit a, b, c = nil
>> raise ArgumentError, "needs exactly one additional argument" if b
>> && c
>> v = b || c
>> raise ArgumentError, "need a numeric parameter" unless
>> v.respond_to?(:to_int)
>> @post "first=#{a}&" + (b.nil? ? "second=ignored&third=#{v}" :
>> "second=#{v}&third=ignored")
>> end
>
> This looks like a great solution!
>
> But if you want to check for numeric parameters, would it be better
> just to use Numeric === v ? I know this isn't the ducky way, but it
> certainly seems more "intentional."

That'd be marginally more readable but to my mind it's overly
cautious: what's the point of using Ruby if you're still going to
follow static typing conventions?

Ellie

Eleanor McHugh
Games With Brains
http://slides.games-with-...
----
raise ArgumentError unless @reality.responds_to? :reason