The basic issue here is abstraction.
An instance of "Robot" in your program is not a
physical object.�� How could it possibly point North,
South, or Nor-nor-west?�� It cannot.
Its location and direction are abstract values
*metaphorically* related to real world notions
like position vectors and velocity vectors.
"North" in this program is not a real thing,
it is an *idea* which could be represented by
'North', 'north', #North, #north, $N, $n,
'Raki',�� 'raki',�� #Raki,�� #raki,�� $R, $r,
137, (0@ -1), a picture of the star Polaris,
the colour red (the conventional colour for
that end of a compass needle which points north),
a sound recording of a lecture by Alfred North
Whitehead, or anything you please, as long as,
inside the program, it *acts* the way *you* want
"north" to act (which is not necessarily the way
the physical direction North acts, and in fact in
this case it most certainly is not).
Locations and movements in a 2D space are, in Smalltalk,
commonly represented by Points.�� "Represented by."
As for this method:
test11_MovesTheRobotForward1SpaceInTheDirectionItIsPointingIncreasesTheYCoordinateOneWhenFacingNorth
������ | result |
������ result := robotSimulatorCalculator
������ ������ moveDirection: 'north'
������ ������ position:
������ ������ ������ (Dictionary new
������ ������ ������ ������ add: 'x' -> 0;
������ ������ ������ ������ add: 'y' -> 0;
������ ������ ������ ������ yourself)
������ ������ instructions: 'A'.
������ self
������ ������ assert: result
������ ������ equals:
������ ������ ������ (Dictionary new
������ ������ ������ ������ add: 'direction' -> 'north';
������ ������ ������ ������ add:
������ ������ ������ ������ ������ 'position'
������ ������ ������ ������ ������ ������ ->
������ ������ ������ ������ ������ ������ ������ (Dictionary new
������ ������ ������ ������ ������ ������ ������ ������ add: 'x' -> 0;
������ ������ ������ ������ ������ ������ ������ ������ add: 'y' -> 1;
������ ������ ������ ������ ������ ������ ������ ������ yourself);
������ ������ ������ ������ yourself)
PLEASE tell me that is not what they are actually using.
Let's start with
�� (Dictionary new)
�������� add: k1 -> v1;
�������� ...
�������� add: kn -> vn;
�������� yourself
Did you know that sending #add: to a dictionary is not
portable?�� Storing actual Association objects inside
Dictionaries was originally an encapsulation error and
remains a performance error, so there are Smalltalks
that do not make that mistake.�� The *portable* way to
make a Dictionary is
������ (Dictionary new)
������������ at: k1 put: v1;
������������ ...
������������ at: kn put: vn;
������������ yourself.
And why in the name of sanity are the keys *strings*
instead of *symbols*?�� This is not Smalltalk.�� It is
Javascript in drag.
implementations.�� For example, I completed the SML track,
and found that the test code ONLY worked with Poly and
not with any of the three SML implementations I already
had on my machine.�� Since you are doing this in Pharo,
I take it that
exercism.io will insist on the Smalltalk
track being done in Pharo, and in that case it is
*nauseating* to use a Dictionary when you could use a
Point.�� Old-fashioned Smalltalk style would have been
to return something like
���� #(<direction> <x> <y>)
e.g. #(north 1 0), and I still prefer that.
In fact *good* Smalltalk style for something like this
would be
test11_MovesTheRobotForward1SpaceInTheDirectionItIsPointingIncreasesTheYCoordinateOneWhenFacingNorth
�� robotSimulatorCalculator
������ moveTo: 0@0;
������ head: #north;
������ obey: 'A'.
�� self assert: robotSimulatorCalculator heading equals: #north.
�� self assert: robotSimulatorCalculator location equals: 0@1.
-- We're starting to get the idea that identifiers like
robotSimulatorCalculator are not a very good idea when
simulatedRobot would do the job as well or better.
(This is also pointing us towards Betrand Meyer's
Command/Query Separation principle, but we shan't
go there today.)
people.�� The test code should use a SMALLTALK interface,
not a warmed-over JAVASCRIPT interface.
Now, how do we map between direction *names* and
direction *points*?�� Well, we have to start by
laying down clearly what we *mean* by the directions.
To move North one step is to add 1 to y and 0 to x.
(We know that from the appalling test case above.)
To move South one step is to add -1 to y and 0 to x.
(South is the opposite of North.)
To move East one step, oh we have a problem.
THIS NEEDS SPELLING OUT.�� And one of the things the
exercism.io exercises are HORRIBLY BAD AT is specifying
the problem.�� Nearly every single exercise I have tried,
I have been unable to tell what the problem is without
examining the test cases, and that is not the way
exercises are supposed to work.�� (Yeah, that's why I'm
screaming about it.�� I've taught a class using exercises
like this that were not of my writing and vague specifications
really upset the students.�� People who had taken the class
under someone else several years before were still angry
about it.)
The geometric classes in Smalltalk were written to support
graphic user interfaces.�� And in user interfaces, the y
coordinate increases DOWN.�� So if we take the compass rose
and rotate it so that North is DOWN, it follows that
West is right and East is left.�� So
To move East one step is to add -1 to x and 0 to y.
To move West one step is to add 1 to x and 0 to y.
The chances are excellent that the problem specification
is inconsistent with this.�� Sigh.�� Let's proceed, though.
North�� 0@1
South�� 0@ -1
East���� -1@0
West���� 1@0
pointToName: aPoint
�� ^aPoint x isZero
�������� ifTrue:�� [aPoint y > 0 ifTrue: [#north] ifFalse: [#south]]
�������� ifFalse: [aPoint x > 0 ifTrue: [#west ] ifFalse: [#east ]]
nameToPoint: aSymbol
�� aSymbol = #north ifTrue: [^0 @ 1].
�� aSymbol = #south ifTrue: [^0 @ -1].
�� aSymbol = #west�� ifTrue: [^1 @ 0].
�� aSymbol = #east�� ifTrue: [^-1 @ 0].
�� aSymbol error: 'not a compass direction in lower case'.
Another problem I had with exercism was a "Space-Age"
exercise where the README.md capitalised the planet names
but test_Space-Age.<whatever> insisted on lower case.
That might well happen here.
Just for grins,
Dictionary>>
asPoint
�� ^(self at: 'x') @ (self at: 'y')
Point>>
asDictionary
�� ^(Dictionary new)
�������� at: 'x' put: self x;
�������� at: 'y' put: self y;
�������� yourself