Search code examples
switch-statementsmalltalkpharo

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 ] ] ] ] ].

Solution

  • 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. :)