Does an Array#apply make any sense at all?

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

On Aug 14, 2007, at 02:40, [email protected] wrote:

I wanted to write a simple method for comparing two paths on a Windows
system.

Is there something wrong with Pathname#==?

On Aug 14, 11:50 am, Eric H. [email protected] wrote:

On Aug 14, 2007, at 02:40, [email protected] wrote:

I wanted to write a simple method for comparing two paths on a Windows
system.

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 (foo.txt and ./

foo.txt)

can refer to the same file.

def ==(other)
return false unless Pathname === other
other.to_s == @path
end
alias === ==
alias eql? ==

nikolai

2007/8/14, [email protected] [email protected]:

It felt like saving the result from #map and then doing the comparison
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

Hi –

On Tue, 14 Aug 2007, Robert K. wrote:

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

On Aug 14, 1:51 pm, “Robert K.” [email protected]
wrote:

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

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

On Aug 14, 2:40 am, “[email protected]
[email protected] wrote:

It felt like saving the result from #map and then doing the comparison
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.

On Aug 14, 1:51 pm, “Robert K.” [email protected]
wrote:

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

On Aug 14, 2:11 pm, Trans [email protected] wrote:

On Aug 14, 2:40 am, “[email protected]

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

On Tue, 14 Aug 2007, [email protected] wrote:

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 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 :wink:

Good question :slight_smile:

David

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.

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

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

mfg, simon … [0] is that actually english?

On Aug 14, 2007, at 04:40, [email protected] wrote:

broken (on all systems):
Could you file a bug on the Ruby tracker? (Or has it been fixed?)

On Aug 14, 2:07 pm, [email protected] wrote:

shift.send(method, *self)

symbolic manner) and applies it to its first element, passing the rest
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 :wink:

nikolai

[email protected] wrote:

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.

nikolai

cheers

Simon

On Aug 14, 8:32 pm, Eric H. [email protected] wrote:

On Aug 14, 2007, at 04:40, [email protected] wrote:

On Aug 14, 11:50 am, Eric H. [email protected] wrote:

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

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

2007/8/15, [email protected] [email protected]:

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

On Aug 14, 2007, at 11:44 PM, [email protected] wrote:

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

On Aug 15, 8:44 pm, William J. [email protected] wrote:

system. My initial algorithm felt very contrived:
def apply(method)
which, to me, looks a lot nicer. Array#apply takes a method (in a
I’d use #inject for your problem:

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

match = [a,b].map {|x| File.expand_path(x).downcase}.uniq.size == 1

Very nice.

nikolai

On Aug 14, 4:38 pm, [email protected] wrote:

On Tue, 14 Aug 2007, [email protected] wrote:

On Aug 14, 2:07 pm, [email protected] wrote:

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.

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

nikolai

On Aug 14, 6:51 am, “Robert K.” [email protected]
wrote:

end
This allowed me to define #same_path? thusly:
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}

match = [a,b].map {|x| File.expand_path(x).downcase}.uniq.size == 1