mentoring contined I hope

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

mentoring contined I hope

Pharo Smalltalk Users mailing list

I would argue against that approach. Make it a requirement that a given API must be used with correct values.
e.g. #hours:minutes: requires hours between 00 and 23 and minutes between 00 and 59.

If you want to convert equivalent time values, use a specific API. For example, many time implementations publicly define a seconds-based API, such as Time class>>#fromSeconds:. You can do the same with your implementation with a class-side #fromMinutes: method. (The corresponding instance methods would be #asSeconds and #asMinutes or something similar.)



Moment, I do not know if I understand you right.

so I would use the given method and use on the instance another function that takes care of the converting to or a valid time object or for example convert it to minutes and on display take care that a valid time is shown.

Do I understand you right ?

I send this personal because I cannot send to the mailing list according to some spam list
Reply | Threaded
Open this post in threaded view
|

Re: mentoring contined I hope

Richard Sargent
Administrator
On Mon, Aug 31, 2020 at 1:57 PM Roelof Wobben <[hidden email]> wrote:

I would argue against that approach. Make it a requirement that a given API must be used with correct values.
e.g. #hours:minutes: requires hours between 00 and 23 and minutes between 00 and 59.

If you want to convert equivalent time values, use a specific API. For example, many time implementations publicly define a seconds-based API, such as Time class>>#fromSeconds:. You can do the same with your implementation with a class-side #fromMinutes: method. (The corresponding instance methods would be #asSeconds and #asMinutes or something similar.)



Moment, I do not know if I understand you right.

so I would use the given method and use on the instance another function that takes care of the converting to or a valid time object or for example convert it to minutes and on display take care that a valid time is shown.

Do I understand you right ?

I don't think you do, but it's a little difficult to determine.

Time hours: 0 minutes: 70 should throw an out of range exception on the second argument.
Time fromMinutes: 70 "should" answer an instance equivalent to 01:00. (I say should in quotes, because Time is a point in time. Duration is something that makes more sense to have Duration minutes: 70 be acceptable.)

This discussion assumes your model of time uses just hours and minutes. Normally, Time includes seconds, and often fractions of seconds. But, you are working on an exercise, so a reduced scope Time may be appropriate.

Rather than talking about Time class>>#fromMinutes:, let's go a little more extreme and discuss a hypothetical Time class>>#fromHours: method. We understand and agree that Time can represent hours from 0 through 23. There is no 24 o'clock. So, if you wrote Time fromHours: 25, what would you expect to get for a result? What would someone who comes along later and uses your code expect to get? It's not well defined. So extrapolate from that case to the other cases. 70 minutes is not a time. "I'll meet you at 70 minutes" said no one ever. :-) ["in 70 minutes" is a duration from the present moment, implying a time 70 minutes in the future.]

I have come across a lot of code in my life. The hardest code to work with and to enhance is code that says it will try to accept whatever it's given. If it wants a number, it will accept a string of digits and "figure out" what number was meant. Life is so much easier when the author says "That's rubbish. Give me a proper number." or "That's too big. Give me a proper number of hours.", etc.

In general, make your APIs precise and predictable. Range check the passed values and complain when they aren't proper. If the clock you are modelling has a precision of minutes, your API is #hours:minutes: and maybe just #hours: for convenience. (In general, I wouldn't do so.) And it's reasonable to have methods to answer the number of increments from the start of the day of your clock's precision and to create a new instance from that number. So, with a precision of minutes, you can have an instance method named e.g. #asMinutes or perhaps #totalMinutes or even #minutesSinceMidnight, and to have a corresponding class method #fromMinutes: or #minutesFromMidnight: which answers the corresponding instance of Time. If your clock has a precision of seconds, which is more normal for us, those "minutes" methods would actually be similarly named "seconds" methods instead. (If your clock has a precision finer than seconds, I would expect to see corresponding methods for the finer gradations of time that would be common, in addition to the various "seconds" methods. e.g. milliseconds or microseconds since midnight. And likely both if the clock resolution was microseconds, just because we do tend to think of milliseconds or microseconds when we talk of more precise times.)


Just as the advice I gave you some months ago was to be unambiguous and precise when naming things, be unambiguous and precise about your APIs (all your methods, really!). People who end up using your code will thank you.





I send this personal because I cannot send to the mailing list according to some spam list
Reply | Threaded
Open this post in threaded view
|

Re: mentoring contined I hope

Pharo Smalltalk Users mailing list
Op 1-9-2020 om 00:22 schreef Richard Sargent:
On Mon, Aug 31, 2020 at 3:10 PM Roelof Wobben <[hidden email]> wrote:
Op 31-8-2020 om 23:45 schreef Richard Sargent:
On Mon, Aug 31, 2020 at 1:57 PM Roelof Wobben <[hidden email]> wrote:

I would argue against that approach. Make it a requirement that a given API must be used with correct values.
e.g. #hours:minutes: requires hours between 00 and 23 and minutes between 00 and 59.

If you want to convert equivalent time values, use a specific API. For example, many time implementations publicly define a seconds-based API, such as Time class>>#fromSeconds:. You can do the same with your implementation with a class-side #fromMinutes: method. (The corresponding instance methods would be #asSeconds and #asMinutes or something similar.)



Moment, I do not know if I understand you right.

so I would use the given method and use on the instance another function that takes care of the converting to or a valid time object or for example convert it to minutes and on display take care that a valid time is shown.

Do I understand you right ?

I don't think you do, but it's a little difficult to determine.

Time hours: 0 minutes: 70 should throw an out of range exception on the second argument.
Time fromMinutes: 70 "should" answer an instance equivalent to 01:00. (I say should in quotes, because Time is a point in time. Duration is something that makes more sense to have Duration minutes: 70 be acceptable.)

This discussion assumes your model of time uses just hours and minutes. Normally, Time includes seconds, and often fractions of seconds. But, you are working on an exercise, so a reduced scope Time may be appropriate.

Rather than talking about Time class>>#fromMinutes:, let's go a little more extreme and discuss a hypothetical Time class>>#fromHours: method. We understand and agree that Time can represent hours from 0 through 23. There is no 24 o'clock. So, if you wrote Time fromHours: 25, what would you expect to get for a result? What would someone who comes along later and uses your code expect to get? It's not well defined. So extrapolate from that case to the other cases. 70 minutes is not a time. "I'll meet you at 70 minutes" said no one ever. :-) ["in 70 minutes" is a duration from the present moment, implying a time 70 minutes in the future.]

I have come across a lot of code in my life. The hardest code to work with and to enhance is code that says it will try to accept whatever it's given. If it wants a number, it will accept a string of digits and "figure out" what number was meant. Life is so much easier when the author says "That's rubbish. Give me a proper number." or "That's too big. Give me a proper number of hours.", etc.

In general, make your APIs precise and predictable. Range check the passed values and complain when they aren't proper. If the clock you are modelling has a precision of minutes, your API is #hours:minutes: and maybe just #hours: for convenience. (In general, I wouldn't do so.) And it's reasonable to have methods to answer the number of increments from the start of the day of your clock's precision and to create a new instance from that number. So, with a precision of minutes, you can have an instance method named e.g. #asMinutes or perhaps #totalMinutes or even #minutesSinceMidnight, and to have a corresponding class method #fromMinutes: or #minutesFromMidnight: which answers the corresponding instance of Time. If your clock has a precision of seconds, which is more normal for us, those "minutes" methods would actually be similarly named "seconds" methods instead. (If your clock has a precision finer than seconds, I would expect to see corresponding methods for the finer gradations of time that would be common, in addition to the various "seconds" methods. e.g. milliseconds or microseconds since midnight. And likely both if the clock resolution was microseconds, just because we do tend to think of milliseconds or microseconds when we talk of more precise times.)




I understad but here im stuck at what the person who made this challenge has said.
And unfortunalty  he/she has deciced that what I said earlier the code schould convert invalid time objects to valid time objects.
If I have made this myself I would make it as you said so  when a time in invalid thrown a error.

I understand. You have constraints. In that case, where you would do validation in #hours:minutes: (class side), do normalization instead. If either argument is out of range (starting with the minutes), keep the remainder and add the excess to the next value. If hours go out of range, discard the excess. You may need to tolerate negative values. The arithmetic is still straight forward. If not, you still need to validate for them.


Roelof


Sorry to be throwing ideas but it is not better to keep everything in for examples minutes . So when I add minutes or substract minutes there does not have to make a new clock all the time.
I could then make the converting from minutes to hours and minitues when I want to display it.
So I have more flexiblility when for example the output format needs to be changed.


Reply | Threaded
Open this post in threaded view
|

Re: mentoring contined I hope

Richard Sargent
Administrator
In reply to this post by Richard Sargent
I recommend against modifying time instances once created. Likewise for dates and timestamps.You don't know if the time instance has been shared with another object. It's best to treat them as immutable once created.

Do not make the mistake of trying to avoid creating new objects. It will lead to contorted code and all sorts of complications. (Of course, be careful not to create a bazillion objects unnecessarily!)

The same advice applies to creating classes. Some languages have a high cost to define a new class, usually psychological. This leaves their programmers "allergic" (or acting as if they were) to creating a new class that would make a solution better. In Smalltalk, a class is just an object.


In terms of how to model time, you could have the time object just store the count of intervals since the start of the day. But, if you want Times with microsecond resolution or nanosecond resolution, you would always be working with a LargeInteger internally.
VA Smalltalk Time has ('hours' 'minutes' 'seconds' 'milliseconds' 'millisecondsFromMidnight')
VisualWorks Time has ('hours' 'minutes' 'seconds' 'milliseconds' 'partialNanosecond')
Pharo (5.0) Time has ('seconds' 'nanos')

It can be useful to trade space efficiency for comprehensibility. Often, focussing on space efficiency results in convoluted code for little other benefit. It's a good idea to start by modelling the concepts that we discuss when describing something. My cookoo clock has an hour hand and a minute hand. It has no concept of seconds, per se. It has a time tick, but whether that is exactly one second, I don't know. Probably not. The pendulum doesn't move that fast. So, if modelling a cookoo clock, modelling the hours and minutes it shows is a reasonable design.

In some respects, modelling your Time as a number of minutes into the day would simplify normalizing the arguments to the instance creation methods. Just compute the requested number of minutes, perform the modulus to limit the size of a day, and you are done. In the absence of a constraint imposed by the author of the problem, you are free to implement a solution that presents the expected results.


On Mon, Aug 31, 2020 at 10:12 PM Roelof Wobben <[hidden email]> wrote:
Op 1-9-2020 om 00:22 schreef Richard Sargent:
On Mon, Aug 31, 2020 at 3:10 PM Roelof Wobben <[hidden email]> wrote:
Op 31-8-2020 om 23:45 schreef Richard Sargent:
On Mon, Aug 31, 2020 at 1:57 PM Roelof Wobben <[hidden email]> wrote:

I would argue against that approach. Make it a requirement that a given API must be used with correct values.
e.g. #hours:minutes: requires hours between 00 and 23 and minutes between 00 and 59.

If you want to convert equivalent time values, use a specific API. For example, many time implementations publicly define a seconds-based API, such as Time class>>#fromSeconds:. You can do the same with your implementation with a class-side #fromMinutes: method. (The corresponding instance methods would be #asSeconds and #asMinutes or something similar.)



Moment, I do not know if I understand you right.

so I would use the given method and use on the instance another function that takes care of the converting to or a valid time object or for example convert it to minutes and on display take care that a valid time is shown.

Do I understand you right ?

I don't think you do, but it's a little difficult to determine.

Time hours: 0 minutes: 70 should throw an out of range exception on the second argument.
Time fromMinutes: 70 "should" answer an instance equivalent to 01:00. (I say should in quotes, because Time is a point in time. Duration is something that makes more sense to have Duration minutes: 70 be acceptable.)

This discussion assumes your model of time uses just hours and minutes. Normally, Time includes seconds, and often fractions of seconds. But, you are working on an exercise, so a reduced scope Time may be appropriate.

Rather than talking about Time class>>#fromMinutes:, let's go a little more extreme and discuss a hypothetical Time class>>#fromHours: method. We understand and agree that Time can represent hours from 0 through 23. There is no 24 o'clock. So, if you wrote Time fromHours: 25, what would you expect to get for a result? What would someone who comes along later and uses your code expect to get? It's not well defined. So extrapolate from that case to the other cases. 70 minutes is not a time. "I'll meet you at 70 minutes" said no one ever. :-) ["in 70 minutes" is a duration from the present moment, implying a time 70 minutes in the future.]

I have come across a lot of code in my life. The hardest code to work with and to enhance is code that says it will try to accept whatever it's given. If it wants a number, it will accept a string of digits and "figure out" what number was meant. Life is so much easier when the author says "That's rubbish. Give me a proper number." or "That's too big. Give me a proper number of hours.", etc.

In general, make your APIs precise and predictable. Range check the passed values and complain when they aren't proper. If the clock you are modelling has a precision of minutes, your API is #hours:minutes: and maybe just #hours: for convenience. (In general, I wouldn't do so.) And it's reasonable to have methods to answer the number of increments from the start of the day of your clock's precision and to create a new instance from that number. So, with a precision of minutes, you can have an instance method named e.g. #asMinutes or perhaps #totalMinutes or even #minutesSinceMidnight, and to have a corresponding class method #fromMinutes: or #minutesFromMidnight: which answers the corresponding instance of Time. If your clock has a precision of seconds, which is more normal for us, those "minutes" methods would actually be similarly named "seconds" methods instead. (If your clock has a precision finer than seconds, I would expect to see corresponding methods for the finer gradations of time that would be common, in addition to the various "seconds" methods. e.g. milliseconds or microseconds since midnight. And likely both if the clock resolution was microseconds, just because we do tend to think of milliseconds or microseconds when we talk of more precise times.)




I understad but here im stuck at what the person who made this challenge has said.
And unfortunalty  he/she has deciced that what I said earlier the code schould convert invalid time objects to valid time objects.
If I have made this myself I would make it as you said so  when a time in invalid thrown a error.

I understand. You have constraints. In that case, where you would do validation in #hours:minutes: (class side), do normalization instead. If either argument is out of range (starting with the minutes), keep the remainder and add the excess to the next value. If hours go out of range, discard the excess. You may need to tolerate negative values. The arithmetic is still straight forward. If not, you still need to validate for them.


Roelof


Sorry to be throwing ideas but it is not better to keep everything in for examples minutes . So when I add minutes or substract minutes there does not have to make a new clock all the time.
I could then make the converting from minutes to hours and minitues when I want to display it.
So I have more flexiblility when for example the output format needs to be changed.


Reply | Threaded
Open this post in threaded view
|

Re: mentoring contined I hope

Pharo Smalltalk Users mailing list
oke, then I would work with hour and minutes as the challenge wanted,
So still on the class side make code that convert any given hour and minute to a valid one ?

Yes. Such code would also be used when doing arithmetic with Times. e.g. if you were to implement an instance method named #addMinutes:, its implementation would be as simple as ^Time hours: self hours minutes: self minutes + anInteger (assuming the argument has that name). The instance creation method would normalize the new requested time, just like Time hours: 0 minutes: 70 would do.

Roelof



On some way I think or I do something wrong or I did misunderstood you.

 I did this in Pharo

Object subclass: #Clock
    instanceVariableNames: 'hour minute'
    classVariableNames: ''
    package: 'Exercise@Clock'

I made a asscesors on the instance side.

then I did this on the class side :

hour: anInteger minute: anInteger2
    self hour: anInteger2 % 60 + anInteger;
    self minute: anInteger2 / 60;
    yourself.

but I see a message that hour and minutes are not known.

Roelof

Reply | Threaded
Open this post in threaded view
|

Re: mentoring contined I hope

Richard Sargent
Administrator
In reply to this post by Richard Sargent
What is the value of self when the error occurred? The debugger should show you this.

Another way to determine this is to look at the code and think about where the sending method is implemented and where the invoked method is implemented (assuming that there really is one).


On Tue, Sep 1, 2020 at 10:11 AM Roelof Wobben <[hidden email]> wrote:
oke, then I would work with hour and minutes as the challenge wanted,
So still on the class side make code that convert any given hour and minute to a valid one ?

Yes. Such code would also be used when doing arithmetic with Times. e.g. if you were to implement an instance method named #addMinutes:, its implementation would be as simple as ^Time hours: self hours minutes: self minutes + anInteger (assuming the argument has that name). The instance creation method would normalize the new requested time, just like Time hours: 0 minutes: 70 would do.

Roelof



On some way I think or I do something wrong or I did misunderstood you.

 I did this in Pharo

Object subclass: #Clock
    instanceVariableNames: 'hour minute'
    classVariableNames: ''
    package: 'Exercise@Clock'

I made a asscesors on the instance side.

then I did this on the class side :

hour: anInteger minute: anInteger2
    self hour: anInteger2 % 60 + anInteger;
    self minute: anInteger2 / 60;
    yourself.

but I see a message that hour and minutes are not known.

Roelof

Reply | Threaded
Open this post in threaded view
|

Re: mentoring contined I hope

Pharo Smalltalk Users mailing list
Op 1-9-2020 om 19:21 schreef Richard Sargent:
What is the value of self when the error occurred? The debugger should show you this.



self is a Clock

Reply | Threaded
Open this post in threaded view
|

Re: mentoring contined I hope

Richard Sargent
Administrator
In reply to this post by Richard Sargent
"self is a Clock" is an imprecise statement. Did you mean that it is an instance of the Clock class or that it is the Clock class itself?

On Tue, Sep 1, 2020 at 10:33 AM Roelof Wobben <[hidden email]> wrote:
Op 1-9-2020 om 19:21 schreef Richard Sargent:
What is the value of self when the error occurred? The debugger should show you this.



self is a Clock

Reply | Threaded
Open this post in threaded view
|

Re: mentoring contined I hope

Pharo Smalltalk Users mailing list
Op 1-9-2020 om 21:11 schreef Richard Sargent:

> You will discover an error when you write your tests, but that's fine.
> It's why you write tests. :-)
>
> #validateTime:minute: is a poor name choice for a few reasons.
> - there is no validation going on. Validation would report an error
> for invalid data.
> - there is nothing to suggest what the first argument is, the
> counterpart to "minute:".
> - it doesn't suggest there will be a new Time ("Clock") created.
> - it's a "verb phrase" saying it will *do* something rather than
> answer something (I'm being vague here. Sorry.)
>
> I suggest #normalizedForHour:minute:.
> It explains that it will normalize the values and that it will answer
> a normalized result (it still isn't great, as it doesn't explain what).
> Perhaps better would be #newNormalizedForHour:minute:.
>
>


Thanks,

I think I found the error

and solved it

hour: anInteger minute: anInteger2
     ^ self new
         hour: (anInteger2 // 60 + anInteger) % 24;
         minute: anInteger2 % 60;
         yourself

instance side :

printOn: aStream
     ^ aStream
         nextPutAll: self hour asTwoCharacterString;
         nextPut: $:;
         nextPutAll: self minute asTwoCharacterString

and the tests are green

now time to sleep and tomorrow time to figure out how to add and
substract minutes.
As I remember it right you gave already the answer to me somewhere in
our conversation

Roelof





I

Reply | Threaded
Open this post in threaded view
|

Re: mentoring contined I hope

Pharo Smalltalk Users mailing list
In reply to this post by Richard Sargent
Op 1-9-2020 om 21:40 schreef Roelof Wobben:
Op 1-9-2020 om 21:11 schreef Richard Sargent:
You will discover an error when you write your tests, but that's fine. It's why you write tests. :-)

#validateTime:minute: is a poor name choice for a few reasons.
- there is no validation going on. Validation would report an error for invalid data.
- there is nothing to suggest what the first argument is, the counterpart to "minute:".
- it doesn't suggest there will be a new Time ("Clock") created.
- it's a "verb phrase" saying it will *do* something rather than answer something (I'm being vague here. Sorry.)

I suggest #normalizedForHour:minute:.
It explains that it will normalize the values and that it will answer a normalized result (it still isn't great, as it doesn't explain what).
Perhaps better would be #newNormalizedForHour:minute:.




Thanks,

I think I found the error

and solved it

hour: anInteger minute: anInteger2
    ^ self new
        hour: (anInteger2 // 60 + anInteger) % 24;
        minute: anInteger2 % 60;
        yourself

instance side :

printOn: aStream
    ^ aStream
        nextPutAll: self hour asTwoCharacterString;
        nextPut: $:;
        nextPutAll: self minute asTwoCharacterString

and the tests are green

now time to sleep and tomorrow time to figure out how to add and substract minutes.
As I remember it right you gave already the answer to me somewhere in our conversation

Roelof


Making it a class method makes things very difficult

Reply | Threaded
Open this post in threaded view
|

Re: mentoring contined I hope

Richard O'Keefe
In reply to this post by Pharo Smalltalk Users mailing list
Richard Sargent is giving good advice that you cannot use in Exercism.
Your code MUST pass the exercism tests and that means it MUST NOT
limit the hour or minute ranges.


On Tue, 1 Sep 2020 at 08:58, Roelof Wobben via Pharo-users <[hidden email]> wrote:

I would argue against that approach. Make it a requirement that a given API must be used with correct values.
e.g. #hours:minutes: requires hours between 00 and 23 and minutes between 00 and 59.

If you want to convert equivalent time values, use a specific API. For example, many time implementations publicly define a seconds-based API, such as Time class>>#fromSeconds:. You can do the same with your implementation with a class-side #fromMinutes: method. (The corresponding instance methods would be #asSeconds and #asMinutes or something similar.)



Moment, I do not know if I understand you right.

so I would use the given method and use on the instance another function that takes care of the converting to or a valid time object or for example convert it to minutes and on display take care that a valid time is shown.

Do I understand you right ?

I send this personal because I cannot send to the mailing list according to some spam list