ruby picture

RCR 216: Provide private methods for changing a Time instance.

Submitted by RabYak (Sun Feb 15 00:02:56 UTC 2004)

Abstract

Expose private methods of the Time class (and perhaps other builtins) which allow changing of the internals.

Problem

The builtin Time class is read-only (except for the gmtime and localtime methods). I have written a class (>MutableTime) which allows times to be edited, but it does this by keeping various instances of Time internally, and passing methods on to them; it does not actually inherit from Time, so there are problems like myMutableTime.kind_of?(Time) returns false. Because there is no way to modify the Time's internals, I cannot inherit from it and properly extend it.

Proposal

The Time class should have additional private methods added; at a minimum, setSeconds( secondsAsfloat ) and setTimeZone( newTimeZone ).

Although these methods need not be used by the Time class itself, they would allow it to be subclassed, giving subclasses proper access to all the instance variables which control the time internally.

Although not part of this RCR, I further recommend that all builtin classes be examined to ensure that they expose the instance variables or methods necessary to subclass them with full control.

Analysis

Without this change, subclass instances of the Time class can only be extended in various read-only manners (primarily formatting functions). The workaround currently used by >MutableTime is effective at achieving the end goals, but it is a shoe-horn hack compared to the proper method of inheritance.

Implementation

My C-code is really, really rusty, but a cursory glance at time.c appears that there already are variables 'tv' and 'gmt' maintained internally, and which are changed as part of various methods. I cannot offer an implementation, but it *seems* like a low-cost effort.

ruby picture
Comments Current voting

This seems like a good idea IMO, but:

  1. why make these methods private instead of public?
  2. precisely what methods do you want to see added?

Also, subclassing builtin classes is often not a good idea (see ).

-- Paul Brannan


You haven't explained the reason why you need mutable version of Time. Why do you need to implement the mutable time which also is subclass of Time? Is the reason important enough to change the standard class behavior? Remember immutable is often better than mutable, not to allow unexpected side effect.

--matz.


I don't understand why I should have to justify why I happen to want to write SubClassX that inherits from ClassX to justify adding hooks to ClassX that are generally useful for any subclassing of it. The change I'm proposing (in particular to the Time class, in general to various Builtins):

  • Has zero impact on existing code
  • Should have (near-)zero impact on performance
  • /Should/ be rather easy to implement
  • Provides functionality previously lacking from the Time class, necessary (IMO) for proper subclassing

But, while I don't believe my particular use needs to be justified, apparently you do :) So:

I like my MutableTime class for code like this:

    def printWeek (day)
        #...
    end
    aDate = MutableTime.now
    (1..24).each{
        printMonth(aDate)
        aDate.month+=1
    }

In general, I want the ability to directly set any value for seconds/minutes/hours/date/month/year. With the Time class as it currently exists, I can sort of easily 'change' the seconds/minutes/hours/date by using the + method and getting a new time class:

    aDate = aDate + 7*DAY_IN_SECONDS
but this both requires annoying calculations, and does not work for months or years. I cannot do this using a custom date= method either, since the return value of such a method is the rvalue, so I can't even hack it using code like:
    aDate = aDate.date+=7
which might return a new Time instance. (And doing something like aDate = aDate.call('date=',aDate.date+7) is an unacceptable hack, for me.)

The only way I can see to do it would be something like:

    class Time
        def setDate(newDate)
            self+newDate*DAY_IN_SECONDS
        end
    end
which would still require me to call it via
    aDate=aDate.setDate(aDate.date+7)

BUT: However flawed the above desire may or may not be in your eyes, what I'm asking for in general is simply a way to access the private instance variables which Time obviously has, but which does not expose even to subclassed methods because they are hidden behind C and not Ruby code. Whether or not you agree with the particular reason I want to extend the Time class in this instance, hopefully you agree that in general a Subclass should be have full access to the private instance variables of the class it inherits from, whether that class was written in Ruby or C.

--RabYak (aka Gavin Kistner)


Oh, and as for which methods in particular I want added, I listed them in the first sentence of the proposal. Really, that would do it :)

--RabYak


RabYak,

You have to understand the language design rationale cannot be "just because". You have to consider the impact of the desgin to the users. From what you mantioned, all you want is actually

  aTime += 7

without allocating new Time object. But this is nearly impossible with Ruby's design of "+=", unless we make a huge change to the language.

Back to Time class, its internal state is actually a single integer that shows seconds since the Epoch (1970-01-01 0:00 GMT). The timeval struct is merely a cache. If you understand this, you might realize it is not that easy to implement methods like "setDate" or "setTimeZone" for Time class.

Ruby's Time class is a representation of C's time_t (note that it's not struct timeval) with fancy formatting methods, so I feel like your proposal is changing Time's role. I feel you are trying to change the Ruby Way (of course this is my personal feeling).

Why do you hate allocation so much, when even numbers are immutable objects?

--matz.


Matz

First, what are you doing spending time writing in-depth responses to newbie RCRs like mine this early? Get back to working on Ruby2 and let your underlings deal with my foolishness! :)

Anyhow...I thought that I had considered the impact to the language. I do personally want (and already have, through my implementation of MutableTime) the ability to do:

  aTime.date+=5
  aTime.month+=12
  #...

...but since the Time object doesn't support this natively, I'm quite happy to say "OK, Time doesn't do what I want; rather than trying to change Time itself to do what I want, I'll just write my own class." Which is what I did, but had to do it without inheritance.

Discussing this more on IRC (which I should have done before bringing a formal RCR) I understand now that this change--providing hooks which allow subclasses to mutate a Time instance--essentially makes Time itself mutable, given the existance of 'instance_eval' and the simple ability for users to do:

  class Time
     def changeMe
        # call the private methods described in this RCR
     end
  end

And while I don't personally see that as bad (it's added flexibility, not changing the core way the 'default' Ruby setup encourages you to program) I do understand that it causes more effect on the language than the near-zero-impact I was envisioning.

The killing blow is the information that the current internal implementation of Time is not mutable, as you say. I must have read the C-code wrong. Given your assertion that it's non-trivial to make this change, I am now voting against my own RCR and will withdraw it.

[And have been convinced by people on IRC that, however well-meaning I may be, I really need to have 1 to 1.5 years' experience with the language before I propose another RCR. I'll try hard to wait :)]


Strongly opposed 0
Opposed 3
Neutral 0
In favor 3
Strongly advocate 0
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .