【问题标题】:How to implement a switch in Pharo Smalltalk如何在 Pharo Smalltalk 中实现开关
【发布时间】:2015-05-28 09:06:18
【问题描述】:

我正在尝试解析命令和 int 以使“乌龟”在棋盘上移动。我有点不知所措,因为它没有抛出异常,而且我什至不知道如何在没有异常的情况下打开调试器。

我的代码:

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

【问题讨论】:

    标签: switch-statement smalltalk pharo


    【解决方案1】:

    您可以按照自己的方式进行操作,并且可以通过在代码中插入self halt 语句来打开调试器。

    通常,ifs 和大小写样式 ifs 是一个不好的信号。所以你可以做的是将功能分解为DownMoveLeftMove等类,然后当你调用时,每个类都会实现自己的功能,例如,一个execute:方法将完成命令所需要的。但是在您的情况下,这种方法会很麻烦;而且,你的动作很琐碎。

    您可以使用带有定义的字典。所以假设你定义了一个实例变量或类变量:

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

    然后在您的代码中,您可以:(moveActions at: bo) value: valInt。这个 sn-p 会给你一个字符串(键)的块(值),然后你用你的整数评估块。

    另一方面,由于动作模式相同,只有消息发生变化,您只能映射字典中的消息名称:

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

    然后你可以让你的海龟执行一个由字符串动态给出的消息:

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

    另外,如果你想依赖你阅读的命令和你发送给海龟的消息之间的相似性,你可以动态地组合消息字符串:

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

    请注意,在您的情况下不建议这样做,因为首先,您正在耦合用户输入和您的代码,即如果您决定将用户命令从 down 更改为 moveDown,您必须将方法名称从 down: 更改为 moveDown:。此外,这种方法允许用户将错误代码“注入”到您的系统中,因为他可以编写像become 42 这样的命令,这将导致代码:

    `turtle perform: #become: with: 42`
    

    这将在海龟对象和 42 之间交换指针。或者你可以考虑更糟糕的情况。但我希望这个元游览对你有好处。 :)

    【讨论】:

    • perform: 需要一个符号作为参数。所以你可以做(bo, ':') asSymbolbo asSymbol asMutator
    • 效果很好!非常感谢!
    • 为什么基于多态的方法会很麻烦?恰恰相反,这将是正确的方法。Smalltalk 没有switch 声明是有原因的;创建一个小的对象层次结构并让它们管理条件行为非常容易。这就是 GoF 所说的“状态模式”,它旨在使代码比许多条件更易于维护。 (当然,字典方法仍然比使用 switch 语句更好。)
    • @AmosM.Carpenter 根据 A. Riel:3.9:不要将操作变成类。怀疑名称是动词或从动词派生的任何类。尤其是那些只有一种有意义的行为(即不计算集合、获取和打印)。询问是否需要将该有意义的行为迁移到某个现有或未发现的类。 此外,要从字符串命令构造类,您必须执行与我描述的相同的技巧,或者创建一个解析器,我认为对于目前的情况来说很麻烦。
    • @Uko:有趣,没听说过那个人。谷歌了一下,假设你的意思是 Arthur J Riel。但是,“3.9”不是直接与他的“2.8 - 一个类应该捕获一个且只有一个关键抽象”以及许多常见模式(例如装饰器和访问者)相矛盾吗?将那些具有不同目的的可爱类合并为一个类不会导致更多的上帝对象吗?不要认为我在我见过的许多单行词上同意瑞尔(他似乎非常喜欢“总是”和“消除”等绝对词),但我没有读过他的书,也许他的原因是那里有更好的解释。
    【解决方案2】:

    在 Smalltalk 中,您不使用 switch 语句。相反,您使用“案例方法”(我认为术语是由 Ken Beck 介绍的,但我不确定)。

    在你的情况下,它会是这样的:

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

    如您所见,这更加清晰,您无需应用“smalltalk 魔术”即可进行高效设计。这还具有清晰、快速执行和易于由编译器 JIT 优化的优点 :)

    【讨论】:

      【解决方案3】:

      仅举另一种可能性:与其自己编写 Parser,不如使用 Smalltalk 中可用的 ParserGenerator 之一(PetitParser、OMeta、Smacc、Xtreams,...)

      这是 Xtreams https://code.google.com/p/xtreams/wiki/Parsing 的示例(链接可能很快就会失效,但我没有任何更新的东西......)可以解析 PEG 格式 (http://en.wikipedia.org/wiki/Parsing_expression_grammar)。

      你首先在一个字符串中定义你的语法:

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

      然后你定义一个解释器来移动海龟:

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

      使用一些方法将解释器连接到 aTurtle,并通过所谓的 pragma(注释)将动作连接到上面的语法规则:

      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
      

      然后你只需创建一个解析器并将它连接到这个解释器:

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

      我让你发现如何指定空格、换行符或命令序列,但你会看到使用这样的框架你的代码是如何解耦和易于扩展的:一个新命令 = 语法描述中的一行 + 解释器中的一个方法用于连接到 Turtle 动作...

      【讨论】:

      • 当一个人询问 switch 语句并得到一个实际回答他的问题的答案,另一个告诉如何将元编程与间接消息一起使用,以及编写解析器的简短指南时,这有点搞笑:)。我们有一个很棒的社区
      • @uko 是的,答案是如何不在 Smalltalk 中编写开关;)
      猜你喜欢
      • 2020-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多