如何在 Pharo Smalltalk 中实现开关
How to implement a switch in Pharo Smalltalk
我正在尝试解析一个命令和一个 int 以在棋盘上进行 "turtle" 移动。我有点不知所措,因为它没有抛出异常,而且我什至不知道如何在没有调试器的情况下打开调试器。
我的代码:
"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 ] ] ] ] ].
您可以按原样进行,并且可以通过在代码中插入 self halt
语句来打开调试器。
通常,if
以及大小写 if
是一个不好的迹象。所以你可以做的是将功能分解成 class 类,如 DownMove
、LeftMove
等等,然后每个 class 将在你调用时实现自己的功能,因为例如,execute:
方法将完全执行命令所需的操作。但是就您而言,这种方法很麻烦;而且你的动作很琐碎
您可以使用带有定义的字典。因此,假设您定义了一个实例变量或 class 变量:
moveActions := {
'down' -> [ :dist | turtle down: dist ] .
'left' -> [ :dist | turtle left: dist ] .
... } asDictionary
然后在您的代码中,您执行:(moveActions at: bo) value: valInt
。此代码段将为您提供字符串(键)的块(值),然后您使用整数评估该块。
另一方面,由于操作模式是相同的,只有消息发生变化,您可以只映射字典中的消息名称:
moveActions := {
'down' -> #down: .
'left' -> #left: .
... } asDictionary
然后你可以让你的乌龟执行一个由字符串动态给出的消息:
`turtle perform: (moveActions at: bo) with: valInt`
另外,如果你想依赖你读取的命令和你发送给海龟的消息之间的相似性,你可以动态地组成消息字符串:
`turtle perform: (bo, ':') asSymbol with: valInt`
请注意,在您的情况下不建议这样做,因为首先,您将用户输入和您的代码结合在一起,即如果您决定将用户命令从 down 到 moveDown,您必须将方法名称从 down:
更改为 moveDown:
。此外,这种方法允许用户将 "inject" 错误代码插入您的系统,因为他可以编写 become 42
之类的命令,这将导致代码:
`turtle perform: #become: with: 42`
这将在 turtle 对象和 42 之间交换指针。或者你可以考虑更糟糕的情况。但我希望这次元游览对您有好处。 :)
在 Smalltalk 中,您不使用 switch 语句。相反,您使用 "case methods"(我认为术语是由 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 magic" 即可获得高效的设计。这还有一个优点,即清晰、执行速度快,并且易于由编译器 和 JIT 优化 :)
仅举另一种可能性:不要自己编写解析器,而是使用 Smalltalk 中提供的一个 ParserGenerator(PetitParser、OMeta、Smacc、Xtreams,...)
这是 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)。
您首先在字符串中定义语法:
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,并通过所谓的 pragmas(注释)将动作连接到上面的语法规则:
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 操作的解释器...
我正在尝试解析一个命令和一个 int 以在棋盘上进行 "turtle" 移动。我有点不知所措,因为它没有抛出异常,而且我什至不知道如何在没有调试器的情况下打开调试器。
我的代码:
"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 ] ] ] ] ].
您可以按原样进行,并且可以通过在代码中插入 self halt
语句来打开调试器。
通常,if
以及大小写 if
是一个不好的迹象。所以你可以做的是将功能分解成 class 类,如 DownMove
、LeftMove
等等,然后每个 class 将在你调用时实现自己的功能,因为例如,execute:
方法将完全执行命令所需的操作。但是就您而言,这种方法很麻烦;而且你的动作很琐碎
您可以使用带有定义的字典。因此,假设您定义了一个实例变量或 class 变量:
moveActions := {
'down' -> [ :dist | turtle down: dist ] .
'left' -> [ :dist | turtle left: dist ] .
... } asDictionary
然后在您的代码中,您执行:(moveActions at: bo) value: valInt
。此代码段将为您提供字符串(键)的块(值),然后您使用整数评估该块。
另一方面,由于操作模式是相同的,只有消息发生变化,您可以只映射字典中的消息名称:
moveActions := {
'down' -> #down: .
'left' -> #left: .
... } asDictionary
然后你可以让你的乌龟执行一个由字符串动态给出的消息:
`turtle perform: (moveActions at: bo) with: valInt`
另外,如果你想依赖你读取的命令和你发送给海龟的消息之间的相似性,你可以动态地组成消息字符串:
`turtle perform: (bo, ':') asSymbol with: valInt`
请注意,在您的情况下不建议这样做,因为首先,您将用户输入和您的代码结合在一起,即如果您决定将用户命令从 down 到 moveDown,您必须将方法名称从 down:
更改为 moveDown:
。此外,这种方法允许用户将 "inject" 错误代码插入您的系统,因为他可以编写 become 42
之类的命令,这将导致代码:
`turtle perform: #become: with: 42`
这将在 turtle 对象和 42 之间交换指针。或者你可以考虑更糟糕的情况。但我希望这次元游览对您有好处。 :)
在 Smalltalk 中,您不使用 switch 语句。相反,您使用 "case methods"(我认为术语是由 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 magic" 即可获得高效的设计。这还有一个优点,即清晰、执行速度快,并且易于由编译器 和 JIT 优化 :)
仅举另一种可能性:不要自己编写解析器,而是使用 Smalltalk 中提供的一个 ParserGenerator(PetitParser、OMeta、Smacc、Xtreams,...)
这是 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)。
您首先在字符串中定义语法:
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,并通过所谓的 pragmas(注释)将动作连接到上面的语法规则:
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 操作的解释器...