Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a switch in Pharo Smalltalk

I'm trying to parse a command and an int to make a "turtle" move on a board. I'm a bit overwhelmed, since it's not throwing an exception, and I can't even figure out how to open the debugger without one.

My code:

"only should begin parsing on new line"
endsWithNewLine:= aTurtleProgram endsWith: String cr.
endsWithNewLine ifTrue:[
    "splits commands based on new lines"
    commands := aTurtleProgram splitOn: String cr.
    commands do:[:com |
        "should split into command and value for command"
        i := com splitOn: ' '.
        "command"
        bo := ci at: 1.
        val := ci at: 2.
        "value for command"
        valInt := val asInteger.
        ^ bo = 'down' "attempted switch"
            ifTrue: [ turtle down: valInt ]
            ifFalse: [
              bo = 'left'
                  ifTrue: [ turtle left: valInt ]
                  ifFalse: [
                    bo = 'right'
                        ifTrue: [ turtle right: valInt ]
                        ifFalse: [
                          bo = 'up'
                              ifTrue: [ turtle up: valInt ]
                              ifFalse: [ self assert: false ] ] ] ] ].
like image 538
sumaksion Avatar asked May 28 '15 09:05

sumaksion


3 Answers

You can do it the way you did it, and you can open a debugger by inserting a self halt statement into your code.

Usually, ifs, and moreover case-sytle ifs, are a bad sign. So what you can do is break the functionality into classes like DownMove, LeftMove and so on, and then each class will implement its own functionality when you call, for example, an execute: method that will do exactly what is needed by the command. But in your case, the approach would be cumbersome; moreover, you have very trivial actions.

You can use a dictionary with definitions. So imagine that you define an instance variable or class variable:

moveActions := {
  'down' -> [ :dist | turtle down: dist ] .
  'left' -> [ :dist | turtle left: dist ] .
  ... } asDictionary

Then in your code, you do: (moveActions at: bo) value: valInt. This snippet will give you a block (value) for a string (key), then you evaluate the block with your integer.

On the other hand, as the action pattern is the same and only message changes, you can map only the message names in the dictionary:

moveActions := {
  'down' -> #down: .
  'left' -> #left: .
  ... } asDictionary

Then you can ask your turtle to perform a message dynamically given by string:

`turtle perform: (moveActions at: bo) with: valInt`

Also, if you want to rely on the similarity between commands that you read and messages that you send to the turtle, you can dynamically compose the message string:

`turtle perform: (bo, ':') asSymbol with: valInt`

Please note that this is not recommended in your case, as, first of all, you are coupling user input and your code, i.e. if you decide to change the user command from down to moveDown, you will have to change your method name from down: to moveDown:. Also, this approach allows the user to "inject" bad code into your system, as he can write a command like become 42, which will result in the code:

`turtle perform: #become: with: 42`

which will swap pointers between the turtle object and 42. Or you can think about even worse cases. But I hope that this meta excursion was good for you. :)

like image 84
Uko Avatar answered Sep 17 '22 15:09

Uko


In Smalltalk you do not use switch statements. Instead, you use "case methods" (I think terminology was introduced by Ken Beck, but I'm not sure).

In your case, it would be something like this:

method1
    "only should begin parsing on new line"
    endsWithNewLine:= aTurtleProgram endsWith: String cr.
    endsWithNewLine ifTrue:[
    "splits commands based on new lines"
    commands := aTurtleProgram splitOn: String cr.
    commands do:[ :eachCommand | 
        | tuple direction value |

        tuple := eachCommand splitOn: ' '.
        direction := tuple first.
        value := tuple second asInteger.
        self moveTurtleDirection: direction value: value ].

moveTurtleDirection: direction value: value
    direction = 'down'  ifTrue: [ ^turtle down: value ].
    direction = 'left'  ifTrue: [ ^turtle left: value ].
    direction = 'right' ifTrue: [ ^turtle right: value ].
    direction = 'up'    ifTrue: [ ^turtle up: value ].

    self error: 'Invalid direction'.

As you can see, this is a lot more clear and you do not need to apply "smalltalk magic" to have an efficient design. This has also the advantage of being clear, fast on execution and easily optimisable by the compiler and the JIT :)

like image 25
EstebanLM Avatar answered Sep 20 '22 15:09

EstebanLM


Just to cite another possibility: instead of writing the Parser by yourself, rather use one of the ParserGenerator available in Smalltalk (PetitParser, OMeta, Smacc, Xtreams, ...)

Here is an example with Xtreams https://code.google.com/p/xtreams/wiki/Parsing (the link is likely to die soon, but I haven't anything newer...) which can parse the PEG format (http://en.wikipedia.org/wiki/Parsing_expression_grammar).

You first define your grammar in a String:

grammar := '
    Number   <- [0-9]+
    Up  <- "up" Number
    Down    <- "down" Number
    Left    <- "left" Number
    Right   <- "right" Number
    Command <- Up / Down / Left / Right
'.

Then you define an interpreter for moving the turtle:

PEGActor subclass: #TurtleInterpreter
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Test-Parser'.

with a few methods to connect the interpreter to aTurtle, and connect an action to the grammar rules above via so called pragmas (annotations):

turtle: aTurtle
    turtle := aTurtle

Number: digits
    <action: 'Number'>
    ^digits inject: 0 into: [ :total :digit | total * 10 + ('0123456789' indexOf: digit) - 1 ]

Up: aNumber
    <action: 'Up'>
    turtle moveUp: aNumber

Down: aNumber
    <action: 'Down'>
    turtle moveDown: aNumber

Left: aNumber
    <action: 'Left'>
    turtle moveLeft: aNumber

Right: aNumber
    <action: 'Right'>
    turtle moveRight: aNumber

Then you just create a parser and connect it to this interpreter:

parser := PEGParser parserPEG
    parse: 'Grammar'
    stream: grammar
    actor: PEGParserParser new.
interpreter := TurtleInterpreter new turtle: Turtle new.    
parser parse: 'Command' stream: 'left24' actor: interpreter.

I let you discover how to specify spaces, newlines or sequence of commands, but you see how decoupled and easily extensible your code can be with such framework: one new command = one line in the grammar description + one method in the interpreter for connecting to a Turtle action...

like image 28
aka.nice Avatar answered Sep 19 '22 15:09

aka.nice