1

Is it possible to override the + operator in smalltalk to accept two params? i.e., I need to also pass in the units for my custom class. Something like:

Number subclass: #NumberWithUnits
instanceVariableNames: 'myName unitTracker'
classVariableNames: ''
poolDictionaries: ''
category: 'hw3'


+ aNumber theUnits
    unitTracker adjustUnits: theUnits.
    ^super + aNumber

Or is there an easier way to do this that I haven't considered?


Additional problem description:

(NumberWithUnits value: 3 unit: #seconds) should give you a NumberWithUnits that represents 3 seconds. But you should also be able to write 3 sec and that should evaluate to a NumberWithUnits (seconds is already taken in Pharo 2.0). The way to do this is to add a sec method to Number, which basically returns (NumberWithUnits value: self unit: #seconds). You can add methods for meters and elephants as well. Then you could write an expression 3 elephants / (1 sec sec) and it would return the right thing. Write a test for it to be sure!

MrDuk
  • 16,578
  • 18
  • 74
  • 133

2 Answers2

5

What you're missing is the order of evaluation/precedence in Smalltalk. It's actually quite a bit simpler than in most other languages:

  1. explicit parentheses ()
  2. unary
  3. binary
  4. keyword
  5. assignment :=

So, you can implement a unary method on Number which gets evaluated before the binary +. A simple example is Number>>negated, which is Smalltalk's version of the unary minus.

At least in Squeak/Pharo (all I've got handy at the moment), date arithmetic is already implemented similarly. Look at Number>>minutes, for example, so you can evaluate things like 5 hours - 3 minutes, which returns a Duration of 0:04:57:00.

Nicholas Riley
  • 43,532
  • 6
  • 101
  • 124
  • Hm, so could you give me an example of when negated would be called during arithmetic? And maybe how I could define something similar? – MrDuk Jun 12 '13 at 01:08
  • Since `+` is a binary message and `negated` is a unary one, it'd be called before the +. Just look at how date arithmetic works, it's exactly what you want. `Duration` is the equivalent of your `NumberWithUnits`, and I guess they're asking you to implement another class to perform the actual calculations, whereas `Duration` itself handles this. – Nicholas Riley Jun 12 '13 at 01:13
  • I don't see how precedence or order of evaluation is relevant here. – poolie Jun 12 '13 at 01:53
  • 1
    Precedence is very important here: if binary operators bound more tightly than unary operators, then `5 hours - 3 minutes` would parse as `((5 hours) - 3) minutes`, rather than the desired `((5 hours) - (3 minutes))`. – amalloy Jun 12 '13 at 02:02
  • 2
    So your point really is that the OP shouldn't look at `a + 3 sec` as `a.add(3, sec)` (to use java-ish syntax) but rather as `a.add(3.sec())` which is how Smalltalk will interpret it anyhow. And from there it flows in to my answer that by the time you get to `+` the units should already be incorporated into the `NumberWithUnits`. – poolie Jun 12 '13 at 04:13
4

I think a more idiomatic way to do this would be to construct a second NumberWithUnits, and then add that.

Then inside your + method you need to reconcile the units of the two things being added, then add their magnitudes.

So something like

a := Measure new: 2 #m
b := Measure new: 10 #mm

a + b

Measure class [

    + other [
        "TODO: check/convert units here"
        resultMagnitude := (a magnitude) + (b magnitude).
        combinedUnits := (a units) * (b units).
        ^Measure new resultMagnitude units: combinedUnits.
    ]

]

See also for example the GNU Smalltalk example of operator overloading.

poolie
  • 9,289
  • 1
  • 47
  • 74
  • Thanks for the tips! Unfortunately, our hw description specifies that we have to use two separate classes - a NumberWithUnits and a UnitsTracker. – MrDuk Jun 12 '13 at 01:11
  • What is a `UnitsTracker`? (What an awful name.) If there is an instance of this for eg "Elephant" or "Kilogram" then you can just have the `NumberWithUnits` hold a reference to an instance. – poolie Jun 12 '13 at 01:16
  • Yeah, awful name. Having not seen the homework, the only thing I can think is maybe it's supposed to be a class that you can use to add units dynamically? Something like `UnitsTracker defineUnit: #feet as: 12 units: #inches`, which would then register the forward and reverse conversions. – Nicholas Riley Jun 12 '13 at 01:22
  • Yeah so UnitsTracker is basically an instanced-dictionary that holds units. The unit is the key, and the value is the associated power. This discussion may shed some light: http://stackoverflow.com/questions/17035060/smalltalk-dictionary-as-calculator – MrDuk Jun 12 '13 at 01:24
  • 1
    Aha, no conversions necessary, it seems, it just accumulates units. The question you referenced gives a pretty good idea of how to implement it. – Nicholas Riley Jun 12 '13 at 01:31
  • I'm thinking maybe I can just write static methods to handle specific units. This is also from the problem description, which may make it easier to implement than I had planned (added to original post). – MrDuk Jun 12 '13 at 01:33
  • OK, that question helps make it clearer, but still, the `NumberWithUnits` (also a bad name) ought to refer to the `UnitTracker` which contains the product of all units applied. – poolie Jun 12 '13 at 01:55
  • @ctote I don't see where you would want static methods here. Things like `3 sec` are a message sent to the integer `3`. – poolie Jun 12 '13 at 02:02
  • The only other option I can think would be to implement Number>>doesNotUnderstand: to turn any unary message into a unit, but I seriously doubt that was intended. – Nicholas Riley Jun 12 '13 at 02:07