iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 15
0
Software Development

30天 Lua重拾筆記系列 第 15

【30天Lua重拾筆記15】基礎2: Label and Goto

本文同步發表於個人網站

Label & goto

這是一個強大的工具,要寫的漂亮並不容易,許多語言禁止了他。
Lua保有他。他很靈活,但你也應該慎重考慮是否真的應該使用。
而我認為,開發人員應該要是自由的,這才有創造的價值,這才能體現思考的藝術。就如同松本行弘說的:「語言體現思考的價值」
如果你的想法最初就是這樣想的,就先寫下來吧!「童子軍原則」別忘了逐漸改得更健壯。

Lua保有C/C++裡,goto的能力,可以跳轉到函數區塊內任意可見的標籤(Label)。標籤的形式由兩個冒號夾成:

:: <Label Name> ::

跳出多層迴圈

BTW, Python 認為如果你需要使用到這個功能,表示你應該在拆分程式碼。

有了這強大的能力可以做啥?可以學像ES6那樣跳出外部迴圈:

九九乘法表只計算到6x4

(()=>{
    outer:
    for(let j = 2; j <= 9; j++){
        let line_output = ""
        for(let i = 1; i <= 9; i++){
            line_output += `${j}x${i}=${j*i},\t`
            if(i > 3 && j > 5){
                console.log(line_output)
                break outer
            }
        }
        console.log(line_output)
    }
})()

Lua類似的作法會是:

for j = 2, 9, 1 do
  for i = 1, 9, 1 do
    io.write(string.format("%2dx%2d=%2d, \t", j, i, j*i))
    
    if i > 3 and j > 5 then
      goto finish
    end
  end
  print()  -- new line
end

::finish::
print() -- new line
2x 1= 2, 	 2x 2= 4, 	 2x 3= 6, 	 2x 4= 8, 	 2x 5=10, 	 2x 6=12, 	 2x 7=14, 	 2x 8=16, 	 2x 9=18, 	
 3x 1= 3, 	 3x 2= 6, 	 3x 3= 9, 	 3x 4=12, 	 3x 5=15, 	 3x 6=18, 	 3x 7=21, 	 3x 8=24, 	 3x 9=27, 	
 4x 1= 4, 	 4x 2= 8, 	 4x 3=12, 	 4x 4=16, 	 4x 5=20, 	 4x 6=24, 	 4x 7=28, 	 4x 8=32, 	 4x 9=36, 	
 5x 1= 5, 	 5x 2=10, 	 5x 3=15, 	 5x 4=20, 	 5x 5=25, 	 5x 6=30, 	 5x 7=35, 	 5x 8=40, 	 5x 9=45, 	
 6x 1= 6, 	 6x 2=12, 	 6x 3=18, 	 6x 4=24, 	

函數內可見的標籤

標籤屬於函式的一部分,只能跳轉到同一個函式內定義的標籤,就算是內部函式跳到外部也不行。所以你不能這樣寫:

function outer()
  local function inner()
    print("inner")
    goto outer_label -- 無法成功跳轉,會報錯
  end

  inner()
  print("outer")
  ::outer_label::
end

錯誤檢查與重起(error and restart)

我很驚訝於Common Lisp的狀態系統。有了label/goto可以來實現一部分這樣的能力。

不過這不是很符合結構化程式,所以看看就好。

function div(a,b)

  ::start::
  a = tonumber(a)
  b = tonumber(b)
  if a and b then
    if b == 0 then
      print("b must be a number, but not 0.")
      goto restart_case
    end
    return a/b
  else
    goto restart_case
  end


  ::restart_case::
  local restart_case = {"Break"}

  if a then
    table.insert(restart_case, "Use Value")
    table.insert(restart_case, "Return Value")
  end

  print("Please Select Restart Case[1-" .. #restart_case .. "]:")
  for i, restart in ipairs(restart_case) do
    print(i, restart)
  end

  select = restart_case[tonumber(io.read())]
  print("Select ", select)
  if select == "Break" then
    goto finish
  elseif select == "Use Value" then
    goto use_value
  elseif select == "Return Value" then
    goto return_value
  else
    goto restart_case
  end

  ::return_value::
  do
    io.write("Return Value: ")
    return tonumber(io.read())
  end

  ::use_value::
  io.write("Use Value: ")
  b = tonumber(io.read())
  goto start

  ::finish::
end

2020-08-31-14-13-02.png

很可惜的這樣做直接進入的debugger,沒辦法給更上層的程式handle。感覺還需要用全局變數/動態變數,否則可能就真的只剩下setjum/longjmp的選項了。

不管怎麼說,沒有Lisp那種程式編輯程式的能力,寫起來真丑?。所以Lisp到底怎麼做到的?


上一篇
【30天Lua重拾筆記14】基礎2: 控制-while、repeat迴圈
下一篇
【30天Lua重拾筆記16】基礎2: 多值返回&具名參數
系列文
30天 Lua重拾筆記36

尚未有邦友留言

立即登入留言