[rcr] String#first / String#last

problem:
I often use a range to extract the beginning/ending letters..
like this "string"[-4, 4] #-> "ring"
It feels somewhat redundant that I have to type the length 2 times.
A very simple operation that easily can confuse people if they
are browsing through others code.. this is a place where one easily can
introduce off-by-one errors.

motivation:
When looking at the Array class I see that it has #first and #last
methods.. just for this purpose, to extract the sub-array either
from the beginning or the ending.
Why not also use #first and #last on String ?

proposal:
"ruby".first(2) #=> "ru"
"ruby".last(3) #=> "uby"

implementation:
class String
  def first(n=nil)
    n ||= 1
    raise TypeError, "cannot convert #{n.class} to Integer" unless n.kind_of?
(Integer)
    raise ArgumentError, "negative string size" if n < 0
    n = [n, self.size].min
    self[0, n]
  end
  def last(n=nil)
    n ||= 1
    raise TypeError, "cannot convert #{n.class} to Integer" unless n.kind_of?
(Integer)
    raise ArgumentError, "negative string size" if n < 0
    n = [n, self.size].min
    self[-n, n]
  end
end

open questions:

Some encodings has variable length chars, where a char may
span between 1 and 5 bytes. This raises the question:
how should the argument to #first be interpreted?
should it interpret as bytes or chars?

Is this a good or a bad proposal?

···

--
Simon Strandgaard

    raise TypeError, "cannot convert #{n.class} to Integer" unless n.kind_of?
(Integer)

svg% ruby -e 'class A; def to_int() 2 end; end; p [1,2,3].first(A.new)'
[1, 2]
svg%

Guy Decoux

class String
def first(n=nil)

      n = (n || 1).to_i

raise ArgumentError, &quot;negative string size&quot; if n &lt; 0
n = \[n, self\.size\]\.min
self\[0, n\]

end
def last(n=nil)

      n = (n || 1).to_i

raise ArgumentError, &quot;negative string size&quot; if n &lt; 0
n = \[n, self\.size\]\.min
self\[\-n, n\]

end
end

Would you say that's better, or no?

Thanks,
T.

···

On Sunday 24 October 2004 09:32 am, Simon Strandgaard wrote:

Simon Strandgaard wrote:

Is this a good or a bad proposal?

I will vote for it. May I propose aliasing left and right to first and last?

Simon Strandgaard

-- shanko

Simon Strandgaard <neoneye@adslhome.dk> wrote in message news:<200410241718.48182.neoneye@adslhome.dk>...

problem:
I often use a range to extract the beginning/ending letters..
like this "string"[-4, 4] #-> "ring"
It feels somewhat redundant that I have to type the length 2 times.
A very simple operation that easily can confuse people if they
are browsing through others code.. this is a place where one easily can
introduce off-by-one errors.

motivation:
When looking at the Array class I see that it has #first and #last
methods.. just for this purpose, to extract the sub-array either
from the beginning or the ending.
Why not also use #first and #last on String ?

Why not just turn String into an Array of characters? It feels like
that's how everyone wants a String to behave anyway.

Dan

Ok.. I think I have fixed this issue now.

irb(main):001:0> 'abc'.last(3)
=> "abc"
irb(main):002:0> class A; def to_int; 2 end; end
=> nil
irb(main):003:0> 'abc'.last(A.new)
=> "bc"
irb(main):004:0> class B; def to_int; 'a' end; end
=> nil
irb(main):005:0> 'abc'.last(B.new)
TypeError: B#to_int should return Integer
        from ./a.rb:22:in `last'
        from (irb):5
irb(main):006:0>

New implementation is here:

class String
  def first(length=nil)
    length ||= 1
    unless length.respond_to?(:to_int)
      raise TypeError, "cannot convert #{length.class} to Integer"
    end
    n = length.to_int
    unless n.kind_of?(Integer)
      raise TypeError, "#{length.class}#to_int should return Integer"
    end
    raise ArgumentError, "negative string size" if n < 0
    n = [n, self.size].min
    self[0, n]
  end
  def last(length=nil)
    length ||= 1
    unless length.respond_to?(:to_int)
      raise TypeError, "cannot convert #{length.class} to Integer"
    end
    n = length.to_int
    unless n.kind_of?(Integer)
      raise TypeError, "#{length.class}#to_int should return Integer"
    end
    raise ArgumentError, "negative string size" if n < 0
    n = [n, self.size].min
    self[-n, n]
  end
end

btw: Is this better?

···

On Sunday 24 October 2004 15:37, ts wrote:

> raise TypeError, "cannot convert #{n.class} to Integer" unless
n.kind_of? S> (Integer)

svg% ruby -e 'class A; def to_int() 2 end; end; p [1,2,3].first(A.new)'
[1, 2]
svg%

Guy Decoux

--
Simon Strandgaard

I have submitted this RCR here.. (and I have mentioned you idea)
http://rcrchive.net/rcr/RCR/RCR283

On IRC, Florian Gross suggested to add #first= and #last=,
that works this way:

"string".last="foo" #=> "strfoo"

It seems as a good idea.

···

On Sunday 24 October 2004 15:54, Shashank Date wrote:

Simon Strandgaard wrote:
> Is this a good or a bad proposal?

I will vote for it. May I propose aliasing left and right to first and
last?

--
Simon Strandgaard

Given the presence of right-to-left languages in the world, and possible some-day support for them, I think first/last is a better choice.

···

On Oct 24, 2004, at 7:54 AM, Shashank Date wrote:

I will vote for it. May I propose aliasing left and right to first and last?

Daniel Berger wrote:

Why not just turn String into an Array of characters? It feels like
that's how everyone wants a String to behave anyway.

I think I'd be opposed to that. But adding a to_a might be good -- I
frequently do a gratuitous split operation for that purpose.

Hal

Simon Strandgaard wrote:

On IRC, Florian Gross suggested to add #first= and #last=,
that works this way:

"string".last="foo" #=> "strfoo"

That is what Christian Neukirchen suggested, in my opinion "string".last="foo" should change the string to "strinfoo". That would also allow "string".last *= 2 to work as expected.

Regards,
Florian Gross

New implementation is here:

class String
  def first(length=nil)
    length ||= 1
    unless length.respond_to?(:to_int)
      raise TypeError, "cannot convert #{length.class} to Integer"
    end
    n = length.to_int
    unless n.kind_of?(Integer)
      raise TypeError, "#{length.class}#to_int should return Integer"
    end
    raise ArgumentError, "negative string size" if n < 0
    n = [n, self.size].min
    self[0, n]
  end
  def last(length=nil)
    length ||= 1
    unless length.respond_to?(:to_int)
      raise TypeError, "cannot convert #{length.class} to Integer"
    end
    n = length.to_int
    unless n.kind_of?(Integer)
      raise TypeError, "#{length.class}#to_int should return Integer"
    end
    raise ArgumentError, "negative string size" if n < 0
    n = [n, self.size].min
    self[-n, n]
  end
end

btw: Is this better?

Yes, I think so, but this might be even more so,

    begin
      n = length.to_int
    rescue NoMethodError
      raise TypeError, "cannot convert #{length.class} to Integer"
    end

also

    unless n.kind_of?(Integer)
      raise TypeError, "#{length.class}#to_int should return Integer"
    end

I don't think this is really needed, since such a bug would be worked out else
where. (That #to_int might be used usefully for something other then
returning an integer is extremely unlikely and thus not worth the check).

T.

···

On Sunday 24 October 2004 09:52 am, Simon Strandgaard wrote:

Gavin Kistner wrote:

···

On Oct 24, 2004, at 7:54 AM, Shashank Date wrote:

I will vote for it. May I propose aliasing left and right to first and last?

Given the presence of right-to-left languages in the world, and possible some-day support for them, I think first/last is a better choice.

Hmmm... never thought of that. Nice catch, Gavin.
So, how about:

   str.left(6)

should show the left 6 chars/codes of the string, regardless.

Another RCR then ?

-- shanko

Hal Fulton wrote:

Daniel Berger wrote:

Why not just turn String into an Array of characters? It feels like
that's how everyone wants a String to behave anyway.

I think I'd be opposed to that. But adding a to_a might be good -- I
frequently do a gratuitous split operation for that purpose.

Pardon me: Of course, there already is a to_a -- but it is line-oriented
like #each. Makes sense in many ways, but I more often need the other.

Hal

Hi,

···

In message "Re: [rcr] String#first / String#last" on Mon, 25 Oct 2004 09:18:51 +0900, Hal Fulton <hal9000@hypermetrics.com> writes:

I think I'd be opposed to that. But adding a to_a might be good -- I
frequently do a gratuitous split operation for that purpose.

It already has "to_a" which works line-wise. Perhaps something called
"explode" in other language is what you want.

              matz.

"Simon Strandgaard" <neoneye@adslhome.dk> schrieb im Newsbeitrag
news:200410241739.14922.neoneye@adslhome.dk...

>
> > raise TypeError, "cannot convert #{n.class} to Integer" unless
> n.kind_of? S> (Integer)
>
> svg% ruby -e 'class A; def to_int() 2 end; end; p

[1,2,3].first(A.new)'

> [1, 2]
> svg%
>
>
> Guy Decoux

Ok.. I think I have fixed this issue now.

irb(main):001:0> 'abc'.last(3)
=> "abc"
irb(main):002:0> class A; def to_int; 2 end; end
=> nil
irb(main):003:0> 'abc'.last(A.new)
=> "bc"
irb(main):004:0> class B; def to_int; 'a' end; end
=> nil
irb(main):005:0> 'abc'.last(B.new)
TypeError: B#to_int should return Integer
        from ./a.rb:22:in `last'
        from (irb):5
irb(main):006:0>

New implementation is here:

class String
  def first(length=nil)
    length ||= 1
    unless length.respond_to?(:to_int)
      raise TypeError, "cannot convert #{length.class} to Integer"
    end
    n = length.to_int
    unless n.kind_of?(Integer)
      raise TypeError, "#{length.class}#to_int should return Integer"
    end
    raise ArgumentError, "negative string size" if n < 0
    n = [n, self.size].min
    self[0, n]
  end
  def last(length=nil)
    length ||= 1
    unless length.respond_to?(:to_int)
      raise TypeError, "cannot convert #{length.class} to Integer"
    end
    n = length.to_int
    unless n.kind_of?(Integer)
      raise TypeError, "#{length.class}#to_int should return Integer"
    end
    raise ArgumentError, "negative string size" if n < 0
    n = [n, self.size].min
    self[-n, n]
  end
end

btw: Is this better?

I'd prefer

class String
  def first(n)
    n = n.to_int
    raise ArgumentError, "negative string size" if n < 0
    self[0,n]
  end

  def last(n)
    n = n.to_int
    raise ArgumentError, "negative string size" if n < 0
    self[-n,n] || self[-length,length]
  end
end

Notes:

- You don't need the type check for n because #to_int will throw if n
does not have it.

- You don't need the min in first, because String# takes care of that
already.

- A default value does not make sense IMHO, because with #first() (i.e.
without arg) I'd
   expect to get the first character ("foo"[0]) which is something
different).

Kind regards

    robert

···

On Sunday 24 October 2004 15:37, ts wrote:

svg% ruby -e 'class A; def to_int() "a" end end; [1,2].first(A.new)'
-e:1:in `first': A#to_int should return Integer (TypeError)
        from -e:1
svg%

Guy Decoux

···

On Sunday 24 October 2004 09:52 am, Simon Strandgaard wrote:

> unless n.kind_of?(Integer)
> raise TypeError, "#{length.class}#to_int should return Integer"
> end

I don't think this is really needed, since such a bug would be worked out else
where. (That #to_int might be used usefully for something other then
returning an integer is extremely unlikely and thus not worth the check).

Believe their is an RCR for #chars

  def chars
    split(//)
  end

T.

···

On Sunday 24 October 2004 11:54 pm, Yukihiro Matsumoto wrote:

Hi,

In message "Re: [rcr] String#first / String#last" > > on Mon, 25 Oct 2004 09:18:51 +0900, Hal Fulton <hal9000@hypermetrics.com> writes:
>I think I'd be opposed to that. But adding a to_a might be good -- I
>frequently do a gratuitous split operation for that purpose.

It already has "to_a" which works line-wise. Perhaps something called
"explode" in other language is what you want.

I see --to behave like Array's methods.

Seems like a lot overhead though when one considers this kind of thing is
going on throughout the system.

I added to my lib, but I did this instead:

    begin
      n = n.to_int.abs
    rescue NoMethodError
      raise TypeError, "cannot convert #{length.class} to positive Integer"
    end

which allowed me to get rid of the negative check too. In the very very very
unlikely case that a non-integer slips through (a String for instance) one
still just gets:

  ArgumentError: comparison of Fixnum with String failed

T.

···

On Sunday 24 October 2004 12:12 pm, ts wrote:

> On Sunday 24 October 2004 09:52 am, Simon Strandgaard wrote:

> > unless n.kind_of?(Integer)
> > raise TypeError, "#{length.class}#to_int should return Integer"
> > end

> I don't think this is really needed, since such a bug would be worked
out else t> where. (That #to_int might be used usefully for something other
then t> returning an integer is extremely unlikely and thus not worth the
check).

svg% ruby -e 'class A; def to_int() "a" end end; [1,2].first(A.new)'
-e:1:in `first': A#to_int should return Integer (TypeError)
        from -e:1
svg%

String#chars would solve the original point as well, at least
semantically:

     "my favorite test string".chars.last # => "g"

...but unless some magic were done, this could be needlessly
inefficient.

    -- Markus

···

On Sun, 2004-10-24 at 21:02, trans. (T. Onoma) wrote:

On Sunday 24 October 2004 11:54 pm, Yukihiro Matsumoto wrote:
> Hi,
>
> In message "Re: [rcr] String#first / String#last" > > > > on Mon, 25 Oct 2004 09:18:51 +0900, Hal Fulton > <hal9000@hypermetrics.com> writes:
> >I think I'd be opposed to that. But adding a to_a might be good -- I
> >frequently do a gratuitous split operation for that purpose.
>
> It already has "to_a" which works line-wise. Perhaps something called
> "explode" in other language is what you want.

Believe their is an RCR for #chars

  def chars
    split(//)
  end

T.

"trans. (T. Onoma)" <transami@runbox.com> schrieb im Newsbeitrag
news:200410250001.59113.transami@runbox.com...

···

On Sunday 24 October 2004 11:54 pm, Yukihiro Matsumoto wrote:
> Hi,
>
> In message "Re: [rcr] String#first / String#last" > > > > on Mon, 25 Oct 2004 09:18:51 +0900, Hal Fulton > <hal9000@hypermetrics.com> writes:
> >I think I'd be opposed to that. But adding a to_a might be good -- I
> >frequently do a gratuitous split operation for that purpose.
>
> It already has "to_a" which works line-wise. Perhaps something called
> "explode" in other language is what you want.

Believe their is an RCR for #chars

  def chars
    split(//)
  end

This does not yield characters but strings with length 1. Note also that
there is String#each_byte which is often sufficient.

Kind regards

    robert