Does an Array#apply make any sense at all?

N

nikolai.weibull

Hi!

I wanted to write a simple method for comparing two paths on a Windows
system. My initial algorithm felt very contrived:

def same_path?(a, b)
a, b = [a, b].map{ |p| File.expand_path(p).downcase }
a == b
end

It felt like saving the result from #map and then doing the comparison
shouldn't be necessary. So I came up with the following solution:

class Array
def apply(method)
shift.send(method, *self)
end
end

This allowed me to define #same_path? thusly:

def same_path?(a, b)
[a, b].map{ |p| File.expand_path(p).downcase }.apply:)==)
end

which, to me, looks a lot nicer. Array#apply takes a method (in a
symbolic manner) and applies it to its first element, passing the rest
of the elements as arguments to the given method.

Any thoughts? Is Array#apply a method that could potentially have
other uses than my specific example above?

nikolai
 
N

nikolai.weibull

Is there something wrong with Pathname#==?

Yes, not that it can't be fixed, but the current definition is sort of
broken (on all systems):

#
# Compare this pathname with +other+. The comparison is string-
based.
# Be aware that two different paths (<tt>foo.txt</tt> and <tt>./
foo.txt</tt>)
# can refer to the same file.
#
def ==(other)
return false unless Pathname === other
other.to_s == @path
end
alias === ==
alias eql? ==

nikolai
 
R

Robert Klemme

2007/8/14 said:
Hi!

I wanted to write a simple method for comparing two paths on a Windows
system. My initial algorithm felt very contrived:

def same_path?(a, b)
a, b = [a, b].map{ |p| File.expand_path(p).downcase }
a == b
end

It felt like saving the result from #map and then doing the comparison
shouldn't be necessary. So I came up with the following solution:

class Array
def apply(method)
shift.send(method, *self)
end
end

This allowed me to define #same_path? thusly:

def same_path?(a, b)
[a, b].map{ |p| File.expand_path(p).downcase }.apply:)==)
end

which, to me, looks a lot nicer. Array#apply takes a method (in a
symbolic manner) and applies it to its first element, passing the rest
of the elements as arguments to the given method.

Any thoughts? Is Array#apply a method that could potentially have
other uses than my specific example above?

Your method should be called "apply!" because it's destructive. I'd
rather not do it that way, i.e. without changing the array at hand.You
can use inject for that.

I'd use #inject for your problem:

match = [a,b].map {|x| File.expand_path(x).downcase}.inject {|x,y| x==y}

Yet another solution is to use one of Pathname's multiple methods (for
example #realpath).

Kind regards

robert
 
N

nikolai.weibull

2007/8/14, (e-mail address removed) <[email protected]>:


I wanted to write a simple method for comparing two paths on a Windows
system. My initial algorithm felt very contrived:
def same_path?(a, b)
a, b = [a, b].map{ |p| File.expand_path(p).downcase }
a == b
end
It felt like saving the result from #map and then doing the comparison
shouldn't be necessary. So I came up with the following solution:
class Array
def apply(method)
shift.send(method, *self)
end
end
This allowed me to define #same_path? thusly:
def same_path?(a, b)
[a, b].map{ |p| File.expand_path(p).downcase }.apply:)==)
end
which, to me, looks a lot nicer. Array#apply takes a method (in a
symbolic manner) and applies it to its first element, passing the rest
of the elements as arguments to the given method.
Any thoughts? Is Array#apply a method that could potentially have
other uses than my specific example above?

Your method should be called "apply!" because it's destructive. I'd
rather not do it that way, i.e. without changing the array at hand.You
can use inject for that.

I'd use #inject for your problem:

match = [a,b].map {|x| File.expand_path(x).downcase}.inject {|x,y| x==y}

Ah, I had no idea inject would take the first item of an array if not
given an argument; thanks! That solves my problem.

nikolai
 
D

dblack

Hi --

2007/8/14 said:
Hi!

I wanted to write a simple method for comparing two paths on a Windows
system. My initial algorithm felt very contrived:

def same_path?(a, b)
a, b = [a, b].map{ |p| File.expand_path(p).downcase }
a == b
end

It felt like saving the result from #map and then doing the comparison
shouldn't be necessary. So I came up with the following solution:

class Array
def apply(method)
shift.send(method, *self)
end
end

This allowed me to define #same_path? thusly:

def same_path?(a, b)
[a, b].map{ |p| File.expand_path(p).downcase }.apply:)==)
end

which, to me, looks a lot nicer. Array#apply takes a method (in a
symbolic manner) and applies it to its first element, passing the rest
of the elements as arguments to the given method.

Any thoughts? Is Array#apply a method that could potentially have
other uses than my specific example above?

Your method should be called "apply!" because it's destructive.

I could be wrong, but I think the convention (at least in core Ruby)
is that ! methods always come in a pair with the non-! version. I
don't think there are any cases where there's just a method called m!
where the ! indicates destructiveness (or other "danger"). All the
unpaired destructive methods have non-! names.

I think this makes sense. If unpaired dangerous methods have !, it
sort of suggests that any time there isn't a !, the method is
non-dangerous, which in turn suggests non-destructive... and that
isn't true.


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
T

Trans

Hi!

I wanted to write a simple method for comparing two paths on a Windows
system. My initial algorithm felt very contrived:

def same_path?(a, b)
a, b = [a, b].map{ |p| File.expand_path(p).downcase }
a == b
end

It felt like saving the result from #map and then doing the comparison
shouldn't be necessary. So I came up with the following solution:

class Array
def apply(method)
shift.send(method, *self)
end
end

This allowed me to define #same_path? thusly:

def same_path?(a, b)
[a, b].map{ |p| File.expand_path(p).downcase }.apply:)==)
end

Your first solution is much easier to understand. In fact I would do:

def same_path?(a, b)
a = File.expand_path(a).downcase
b = File.expand_path(b).downcase
a == b
end

It's very clear and actually should be a tad faster. LOC isn't
everything!

But if you just can't abide by more that one line:

def same_path?(a, b)
File.expand_path(a).downcase == File.expand_path(b).downcase
end

HTH,
T.
 
N

nikolai.weibull

Yet another solution is to use one of Pathname's multiple methods (for
example #realpath).

The problem is that Pathname's comparison is case sensitive.

nikolai
 
N

nikolai.weibull

On Aug 14, 2:40 am, "(e-mail address removed)"
def same_path?(a, b)
[a, b].map{ |p| File.expand_path(p).downcase }.apply:)==)
end

Your first solution is much easier to understand. In fact I would do:
def same_path?(a, b)
File.expand_path(a).downcase == File.expand_path(b).downcase
end

It's not about the line count but about executing the same set of
functions/methods on both a and b. With the #inject or #apply
solution it's clear that both a and b undergo the same conversion.

nikolai
 
N

nikolai.weibull

Hi --



2007/8/14 said:
Hi!
I wanted to write a simple method for comparing two paths on a Windows
system. My initial algorithm felt very contrived:
def same_path?(a, b)
a, b = [a, b].map{ |p| File.expand_path(p).downcase }
a == b
end
It felt like saving the result from #map and then doing the comparison
shouldn't be necessary. So I came up with the following solution:
class Array
def apply(method)
shift.send(method, *self)
end
end
This allowed me to define #same_path? thusly:
def same_path?(a, b)
[a, b].map{ |p| File.expand_path(p).downcase }.apply:)==)
end
which, to me, looks a lot nicer. Array#apply takes a method (in a
symbolic manner) and applies it to its first element, passing the rest
of the elements as arguments to the given method.
Any thoughts? Is Array#apply a method that could potentially have
other uses than my specific example above?
Your method should be called "apply!" because it's destructive.

I could be wrong, but I think the convention (at least in core Ruby)
is that ! methods always come in a pair with the non-! version. I
don't think there are any cases where there's just a method called m!
where the ! indicates destructiveness (or other "danger"). All the
unpaired destructive methods have non-! names.

Ugh. This was so NOT about whether to call the method Array#apply or
Array#apply!. I just wrote the absolute shortest solution I could
think of. I didn't suggest that it was the final version. Yes,
seeing as how my definition mutates its target, Array#apply! is the
appropriate name. I simply didn't want to clutter the definition
thusly:

class Array
def apply(method)
first.send(method, *self[1..-1])
end
end

(which sort of begs the question why we don't have Array#rest ;-)

nikolai
 
D

dblack

Ugh. This was so NOT about whether to call the method Array#apply or
Array#apply!.

I know -- I was just replying to a tangential point that Robert
raised.
I just wrote the absolute shortest solution I could
think of. I didn't suggest that it was the final version. Yes,
seeing as how my definition mutates its target, Array#apply! is the
appropriate name.

I would still disagree, on the grounds that ! doesn't mean that a
method changes its receiver; it means the method is "dangerous"
(Matz's definition), and that only has meaning in reference to a
non-dangerous but otherwise equivalent method.
I simply didn't want to clutter the definition
thusly:

class Array
def apply(method)
first.send(method, *self[1..-1])
end
end

(which sort of begs the question why we don't have Array#rest ;-)

Good question :)


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
D

dblack

Hi --

That is true for Array#delete. delete is always destructive, there is no
need to flag that.

And push, pop, <<, concat, replace, clear.... There's definitely no
general guideline that destructive methods end in !.

(Of course String has both delete and delete! :)
But there is no need for your method to be destructive, so you need to
tell the world about it.
I think this makes sense. If unpaired dangerous methods have !, it
sort of suggests that any time there isn't a !, the method is
non-dangerous, which in turn suggests non-destructive... and that
isn't true.

It should simply be clear from the name[0] if a method is destructive.
Often you need the ! for that, in some cases it's obvious without the !.

I guess I take my cue from the Ruby core/standard language, where
there's no use of !, as far as I know, except to distinguish a
"dangerous" method from its non-dangerous partner. I don't know of any
case where a method is just considered "dangerous" in the abstract,
without comparison to another method.

Basically, I can't find any object for which this:

obj.methods.grep(/!/).detect {|m| not obj.respond_to?(m.delete('!')) }

or similar tests with private methods, etc., returns a value.


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
E

Eric Hodel

Yes, not that it can't be fixed, but the current definition is sort of
broken (on all systems):

Could you file a bug on the Ruby tracker? (Or has it been fixed?)
 
S

Simon Kröger

On Aug 14, 2:40 am, "(e-mail address removed)"
def same_path?(a, b)
[a, b].map{ |p| File.expand_path(p).downcase }.apply:)==)
end
Your first solution is much easier to understand. In fact I would do:
def same_path?(a, b)
File.expand_path(a).downcase == File.expand_path(b).downcase
end

It's not about the line count but about executing the same set of
functions/methods on both a and b. With the #inject or #apply
solution it's clear that both a and b undergo the same conversion.

perhaps you should just give the functions/methods a name:

def normalize path
File.expand_path(path).downcase
end

def same_path?(a, b)
normalize(a) == normalize(b)
end

Of course we are drifting far away from the topic of this thread.

More on topic: I would think an apply method would apply a function to each
member of the array (like map) - just from the sound of word.

cheers

Simon
 
N

nikolai.weibull

On Aug 14, 2007, at 04:40, (e-mail address removed) wrote:
Could you file a bug on the Ruby tracker? (Or has it been fixed?)

Well, the documentation is pretty clear about what it does. Even
though I'd considered it bugged. Perhaps the semantics of Pathname#==
should be clarified before we post any change suggestions.

So, should Pathname#== respect the platforms case insensitivity, for
example, on Windows and on Mac OS?

Should Pathname#== try hard to make sure that both arguments are as
complete and clean as possible, for example, by calling
Pathname#expand_path on both arguments? This would have to take into
account that Pathname#== currently has taken any argument that
responds to #to_s.

nikolai
 
N

nikolai.weibull

I know -- I was just replying to a tangential point that Robert
raised.
I would still disagree, on the grounds that ! doesn't mean that a
method changes its receiver; it means the method is "dangerous"
(Matz's definition), and that only has meaning in reference to a
non-dangerous but otherwise equivalent method.

Ah, OK. Yeah, I suppose that's true. Still, my second suggestion for
how to implement Array#apply is probably better.

nikolai
 
R

Robert Klemme

2007/8/15 said:
Well, the documentation is pretty clear about what it does. Even
though I'd considered it bugged. Perhaps the semantics of Pathname#==
should be clarified before we post any change suggestions.

So, should Pathname#== respect the platforms case insensitivity, for
example, on Windows and on Mac OS?

Should Pathname#== try hard to make sure that both arguments are as
complete and clean as possible, for example, by calling
Pathname#expand_path on both arguments? This would have to take into
account that Pathname#== currently has taken any argument that
responds to #to_s.

I dislike this kind of automation behind the scenes because there may
be situations where you do not want this behavior. Also keep in mind
that ultimately a safe comparison needs to access the file system
which has a significant performance impact.

I'd prefer either a method like #normalize which does all the
necessary transformations which enable == to compare for identical
file system paths or add a method that does the comparison explicitly.
Maybe #realpath can be adjusted to deliver this.

Just my 0.02EUR...

Kind regards

robert
 
S

Simon Krahnke

* said:
And push, pop, <<, concat, replace, clear...

Which all have to be modifying. With the exception of concat. (The Unix
cat command isn't modifying.)
It should simply be clear from the name[0] if a method is destructive.
Often you need the ! for that, in some cases it's obvious without the !.

I guess I take my cue from the Ruby core/standard language, where
there's no use of !, as far as I know, except to distinguish a
"dangerous" method from its non-dangerous partner.

That's because of the completeness of the library. Whenever there can be
a non-modifying partner, there is one.

When you just define a modifying method, is not right to name it without
a ! just because you choose not to define a non-modifying version.

What if some other developer wants to add the non-modifying version?
He would have to rename your version.

mfg, simon .... l
 
D

dblack

Hi --

* said:
And push, pop, <<, concat, replace, clear...

Which all have to be modifying. With the exception of concat. (The Unix
cat command isn't modifying.)
It should simply be clear from the name[0] if a method is destructive.
Often you need the ! for that, in some cases it's obvious without the !.

I guess I take my cue from the Ruby core/standard language, where
there's no use of !, as far as I know, except to distinguish a
"dangerous" method from its non-dangerous partner.

That's because of the completeness of the library. Whenever there can be
a non-modifying partner, there is one.

! does not mean modifying; it means "dangerous".
When you just define a modifying method, is not right to name it without
a ! just because you choose not to define a non-modifying version.

I would never add a ! to a method name solely because it changes the
receiver. That's a re-invention of what ! is for.
What if some other developer wants to add the non-modifying version?
He would have to rename your version.

I think that's a solution in search of a problem. Just name your
methods clearly, without !. Then, if you (or someone else) want to add
a "dangerous" version, you can write the ! method.

Also -- once again -- ! does not mean that the receiver is changed. It
means "dangerous"; it's a warning to the programmer that the method is
going to do something that the non-! version doesn't do, and that
might involve the unexpected.

! is not a general-purpose receiver-modifying flag, and I would very
strongly discourage its adaptation to that use. It's not necessary,
and it dilutes the original purpose of !.


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
M

Mark Day

So, should Pathname#== respect the platforms case insensitivity, for
example, on Windows and on Mac OS?

Seems like a bad idea to me. Case sensitivity is not a property of
the platform; it is a property of a given file system. It's easy to
have some case sensitive and some case insensitive file systems on
many platforms, including Mac OS and Windows.

Besides, there is more to pathname equivalence than case sensitivity.
For example, some older file systems defined case insensitivity based
on the letters A through Z, completely ignoring other characters.
Worse, they often just assumed 0x41 == 0x61, even for multi-byte
character encodings (meaning two unrelated characters were considered
equal). Some file systems take diacriticals into account, and some
don't.

In the end, you generally have to ask the file system to look up the
path and return some unique identifier (such as a combination of
device identifier and node number). Then you can (usually) compare
those unique identifiers. That only works for paths that already
exist. Predicting whether a non-existant path will be equivalent to
some other path is hard.

-Mark
 

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,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top