Tại sao Lua không có tuyên bố tiếp tục của người Viking?


142

Tôi đã giao dịch rất nhiều với Lua trong vài tháng qua và tôi thực sự thích hầu hết các tính năng nhưng tôi vẫn còn thiếu một số thứ trong số đó:

  • Tại sao không có continue?
  • Có cách giải quyết nào cho nó?

12
Vì câu hỏi này đã được hỏi, Lua nhận được một gototuyên bố có thể được sử dụng để tiếp tục thực hiện. Xem câu trả lời dưới đây.
lhf

Câu trả lời:


69

Trong Lua 5.2, cách giải quyết tốt nhất là sử dụng goto:

-- prints odd numbers in [|1,10|]
for i=1,10 do
  if i % 2 == 0 then goto continue end
  print(i)
  ::continue::
end

Điều này được hỗ trợ trong LuaJIT kể từ phiên bản 2.0.1


46
Tôi hy vọng họ bao gồm một continuengày thực tế . Sự gotothay thế trông không đẹp lắm và cần nhiều đường hơn. Ngoài ra, điều đó sẽ không gây rắc rối nếu bạn có nhiều hơn một vòng lặp thực hiện điều này trong một chức năng, cả hai đều có ::continue::? Tạo một tên trên mỗi vòng lặp có vẻ không phải là một việc nên làm.
ET

65

Cách mà ngôn ngữ quản lý phạm vi từ vựng tạo ra các vấn đề bao gồm cả gotocontinue. Ví dụ,

local a=0
repeat 
    if f() then
        a=1 --change outer a
    end
    local a=f() -- inner a
until a==0 -- test inner a

Khai báo local abên trong thân vòng lặp che dấu biến bên ngoài được đặt tên avà phạm vi của địa phương đó mở rộng qua điều kiện của untilcâu lệnh để điều kiện này được kiểm tra trong cùng a.

Nếu continuetồn tại, nó sẽ phải bị hạn chế về mặt ngữ nghĩa để chỉ có giá trị sau khi tất cả các biến được sử dụng trong điều kiện đã đi vào phạm vi. Đây là một điều kiện khó để ghi lại cho người dùng và thực thi trong trình biên dịch. Đề xuất khác nhau xung quanh vấn đề này đã được thảo luận, trong đó có câu trả lời đơn giản không cho phép continuevới repeat ... untilphong cách của vòng lặp. Cho đến nay, không ai có trường hợp sử dụng đủ hấp dẫn để đưa chúng vào ngôn ngữ.

Công việc xung quanh nói chung là đảo ngược điều kiện sẽ khiến cho một continuelệnh được thực thi và thu thập phần còn lại của thân vòng lặp trong điều kiện đó. Vì vậy, vòng lặp sau đây

-- not valid Lua 5.1 (or 5.2)
for k,v in pairs(t) do
  if isstring(k) then continue end
  -- do something to t[k] when k is not a string
end

có thể được viết

-- valid Lua 5.1 (or 5.2)
for k,v in pairs(t) do
  if not isstring(k) then 
    -- do something to t[k] when k is not a string
  end
end

Nó là đủ rõ ràng, và thường không phải là một gánh nặng trừ khi bạn có một loạt các culls phức tạp kiểm soát hoạt động vòng lặp.


5
Đến từ một nền tảng python đây là một câu trả lời khó hiểu bởi vì mọi phạm vi ở đó đã biết các biến cục bộ của nó là gì trước khi chạy. Tức là tôi mong đợi một lỗi biến cục bộ không liên kết trong trường hợp đạt tới until....
ubershmekel

2
Có rất nhiều cuộc thảo luận về vấn đề này trong cộng đồng Lua trước khi đưa gotovào Lua 5.2. Đương nhiên, gotocó cùng một vấn đề. Cuối cùng họ đã quyết định rằng bất cứ chi phí nào trong thời gian chạy và / hoặc mã để bảo vệ chống lại nó đều có giá trị lợi ích của việc có một linh hoạt gotocó thể được sử dụng để mô phỏng cả hai continuevà đa cấp break. Bạn sẽ phải tìm kiếm tài liệu lưu trữ danh sách Lua cho các chủ đề liên quan để có được thông tin chi tiết. Vì họ đã giới thiệu goto, rõ ràng nó không thể vượt qua.
RBerteig

71
Không có gì "đủ rõ ràng" về việc viết mã mà không tiếp tục. Đó là một sai lầm mới làm quen với mã lồng trong một điều kiện trong đó nên sử dụng tiếp tục và cần phải viết mã xấu như thế không nên nhận được bất kỳ sự đồng cảm nào. Hoàn toàn không có lý do.
Glenn Maynard

4
Giải thích này không có ý nghĩa. locallà chỉ thị của trình biên dịch - không có vấn đề gì về thời gian chạy giữa localvà sử dụng biến - bạn không cần thay đổi bất cứ điều gì trong trình biên dịch để duy trì hành vi phạm vi tương tự. Vâng, điều này có thể không quá rõ ràng và cần một số tài liệu bổ sung, nhưng, để nhắc lại một lần nữa, nó yêu cầu thay đổi ZERO trong trình biên dịch. repeat do break end until trueví dụ trong câu trả lời của tôi đã tạo ra chính xác cùng mã byte mà trình biên dịch sẽ tiếp tục, sự khác biệt duy nhất là với continuebạn sẽ không cần thêm cú pháp xấu xí để sử dụng nó.
Oleg V. Volkov

7
Rằng bạn có thể kiểm tra biến bên trong nói về thiết kế thiếu sót. Điều kiện nằm ngoài phạm vi bên trong và nó không nên có quyền truy cập vào các biến trong đó. Hãy xem xét tương đương trong C: do{int i=0;}while (i == 0);fail hoặc trong C ++: do int i=0;while (i==0);cũng fail ("không được khai báo trong phạm vi này"). Quá muộn để thay đổi điều đó bây giờ ở Lua, thật không may.
Pedro Gimeno

45

Bạn có thể bọc thân vòng lặp bổ sung repeat until truevà sau đó sử dụng do break endbên trong để tiếp tục. Đương nhiên, bạn sẽ cần thiết lập các cờ bổ sung nếu bạn cũng có ý định thực sự breakthoát khỏi vòng lặp.

Điều này sẽ lặp 5 lần, in 1, 2 và 3 mỗi lần.

for idx = 1, 5 do
    repeat
        print(1)
        print(2)
        print(3)
        do break end -- goes to next iteration of for
        print(4)
        print(5)
    until true
end

Cấu trúc này thậm chí còn chuyển thành một opcode theo nghĩa đen JMPtrong Lua bytecode!

$ luac -l continue.lua 

main <continue.lua:0,0> (22 instructions, 88 bytes at 0x23c9530)
0+ params, 6 slots, 0 upvalues, 4 locals, 6 constants, 0 functions
    1   [1] LOADK       0 -1    ; 1
    2   [1] LOADK       1 -2    ; 3
    3   [1] LOADK       2 -1    ; 1
    4   [1] FORPREP     0 16    ; to 21
    5   [3] GETGLOBAL   4 -3    ; print
    6   [3] LOADK       5 -1    ; 1
    7   [3] CALL        4 2 1
    8   [4] GETGLOBAL   4 -3    ; print
    9   [4] LOADK       5 -4    ; 2
    10  [4] CALL        4 2 1
    11  [5] GETGLOBAL   4 -3    ; print
    12  [5] LOADK       5 -2    ; 3
    13  [5] CALL        4 2 1
    14  [6] JMP         6   ; to 21 -- Here it is! If you remove do break end from code, result will only differ by this single line.
    15  [7] GETGLOBAL   4 -3    ; print
    16  [7] LOADK       5 -5    ; 4
    17  [7] CALL        4 2 1
    18  [8] GETGLOBAL   4 -3    ; print
    19  [8] LOADK       5 -6    ; 5
    20  [8] CALL        4 2 1
    21  [1] FORLOOP     0 -17   ; to 5
    22  [10]    RETURN      0 1

4
Câu trả lời này là tốt, nhưng vẫn cần 3 dòng thay vì chỉ một. (nếu "tiếp tục" được hỗ trợ đúng cách) Mặc dù vậy, nó an toàn hơn và an toàn hơn một nhãn goto, vì đối với tên đó, có thể cần phải tránh các xung đột cho các vòng lặp lồng nhau.
ET

3
tuy nhiên, điều đó tránh được vấn đề "thực sự" với goto ở chỗ bạn không phải phát minh ra một mã định danh / nhãn mới cho mỗi psuedo-continue và nó ít bị lỗi hơn khi mã được sửa đổi theo thời gian. Tôi đồng ý rằng tiếp tục sẽ hữu ích , nhưng IMO này là điều tốt nhất tiếp theo (và nó thực sự cần hai dòng để lặp lại / cho đến khi so với "tiếp tục" chính thức hơn và thậm chí sau đó, nếu bạn quan tâm đến dòng đó số lượng bạn luôn có thể viết "lặp lại" và "cho đến khi kết thúc thực sự", ví dụ: gist.github.com/wilson0x4d/f8410719033d1e0ef771 )
Shaun Wilson

1
Rất vui khi thấy mọi người thực sự xem xét hiệu suất và thậm chí cung cấp luacđầu ra trên SO! Có một upvote xứng đáng :)
DarkWiiPlayer

16

Trực tiếp từ chính nhà thiết kế của Lua :

Mối quan tâm chính của chúng tôi với "tiếp tục" là có một số cấu trúc điều khiển khác (theo quan điểm của chúng tôi) ít nhiều quan trọng như "tiếp tục" và thậm chí có thể thay thế nó. (Ví dụ: ngắt với các nhãn [như trong Java] hoặc thậm chí là một goto chung hơn.) "Tiếp tục" dường như không đặc biệt hơn các cơ chế cấu trúc điều khiển khác, ngoại trừ việc nó có trong nhiều ngôn ngữ. (Perl thực sự có hai câu lệnh "tiếp tục", "tiếp theo" và "làm lại". Cả hai đều hữu ích.)


5
Tôi yêu sự tiếp nhận: "Cả hai đều hữu ích" ngay sau khi giải thích "chúng tôi sẽ không làm điều đó"
David Ljung Madison Stellar

2
Đó là lưu ý phạm vi mà họ đang tìm cách giải quyết khi họ thực hiện nó, bằng cách thêm cấu trúc "goto" trong 5.2 (chưa được phát hành khi câu trả lời này được viết). Xem câu trả lời này từ năm 2012 , sau khi 5.2.0 được phát hành.
Stuart P. Bentley

3
Phải - bởi vì 'goto' được công nhận là một cấu trúc lập trình phong nha. (cuối mỉa mai) Ah tốt.
David Ljung Madison Stellar

2
Nhưng nó không có vẻ hợp lý hơn "Tôi chỉ quên đưa continuevào Lua, xin lỗi."
neoedmund

16

Phần thứ nhất được trả lời trong câu hỏi thường gặp khi bị giết ra nhọn.

Đối với một cách giải quyết, bạn có thể bọc phần thân của vòng lặp trong một hàm và returntừ đó, ví dụ:

-- Print the odd numbers from 1 to 99
for a = 1, 99 do
  (function()
    if a % 2 == 0 then
      return
    end
    print(a)
  end)()
end

Hoặc nếu bạn muốn cả hai breakcontinuechức năng, hãy để chức năng cục bộ thực hiện kiểm tra, ví dụ:

local a = 1
while (function()
  if a > 99 then
    return false; -- break
  end
  if a % 2 == 0 then
    return true; -- continue
  end
  print(a)
  return true; -- continue
end)() do
  a = a + 1
end

15
Xin đừng. Bạn tạo môi trường đóng trên mỗi lần lặp và đây là sự lãng phí rất lớn cho bộ nhớ và chu trình GC.
Oleg V. Volkov

4
hãy kiểm tra collectgarbage("count")ngay cả sau 100 lần thử đơn giản của bạn và sau đó chúng tôi sẽ nói chuyện. Tối ưu hóa "sớm" như vậy đã lưu một dự án tải trọng cao từ việc khởi động lại mỗi phút vào tuần trước.
Oleg V. Volkov

4
@ OlegV.Volkov trong khi ví dụ này không đặt tải tương đối cao lên GC, nó không bị rò rỉ - Tất cả các lần đóng tạm thời sẽ được thu thập. Tôi không biết về dự án của bạn nhưng IME hầu hết các lần khởi động lại là do rò rỉ.
vây

9

Tôi chưa bao giờ sử dụng Lua trước đây, nhưng tôi đã lấy nó và nghĩ ra điều này:

http://www.luafaq.org/

Kiểm tra câu hỏi 1.26 .

Đây là một khiếu nại phổ biến. Các tác giả Lua cảm thấy rằng tiếp tục chỉ là một trong một số cơ chế dòng điều khiển mới có thể (thực tế là nó không thể hoạt động với các quy tắc phạm vi lặp lại / cho đến khi là một yếu tố phụ.)

Trong Lua 5.2, có một câu lệnh goto có thể dễ dàng sử dụng để thực hiện cùng một công việc.


7

Chúng ta có thể đạt được nó như dưới đây, nó sẽ bỏ qua các số chẵn

local len = 5
for i = 1, len do
    repeat 
        if i%2 == 0 then break end
        print(" i = "..i)
        break
    until true
end

O / P:

i = 1
i = 3
i = 5

5

Chúng tôi đã gặp kịch bản này nhiều lần và chúng tôi chỉ cần sử dụng một lá cờ để mô phỏng tiếp tục. Chúng tôi cũng cố gắng tránh sử dụng các câu lệnh goto.

Ví dụ: Mã dự định in các câu lệnh từ i = 1 đến i = 10 ngoại trừ i = 3. Ngoài ra, nó cũng in "vòng lặp bắt đầu", kết thúc vòng lặp "," nếu bắt đầu "và" nếu kết thúc "để mô phỏng các câu lệnh lồng nhau khác tồn tại trong mã của bạn.

size = 10
for i=1, size do
    print("loop start")
    if whatever then
        print("if start")
        if (i == 3) then
            print("i is 3")
            --continue
        end
        print(j)
        print("if end")
    end
    print("loop end")
end

đạt được bằng cách kèm theo tất cả các câu lệnh còn lại cho đến phạm vi kết thúc của vòng lặp với cờ kiểm tra.

size = 10
for i=1, size do
    print("loop start")
    local continue = false;  -- initialize flag at the start of the loop
    if whatever then
        print("if start")
        if (i == 3) then
            print("i is 3")
            continue = true
        end

        if continue==false then          -- test flag
            print(j)
            print("if end")
        end
    end

    if (continue==false) then            -- test flag
        print("loop end")
    end
end

Tôi không nói rằng đây là cách tiếp cận tốt nhất nhưng nó hoạt động hoàn hảo với chúng tôi.


3

Một lần nữa với đảo ngược, bạn chỉ cần sử dụng mã sau đây:

for k,v in pairs(t) do
  if not isstring(k) then 
    -- do something to t[k] when k is not a string
end

Vấn đề với nghịch đảo là thường xuyên hơn không có nhiều điều kiện trong một chuỗi (chẳng hạn như để xác thực đầu vào của người dùng). Và bởi vì có thể cần phải đoản mạch tại bất kỳ điểm nào trên đường đi, đảo ngược có nghĩa là phải lồng các điều kiện liên tục (thay vì "điều này có tệ không? Sau đó thoát ra, điều này có tệ không? bạn kết thúc với mã như thế "là thế này sao rồi là thế này sao rồi là thế này sao rồi làm điều này??" mà là rất quá đáng.
Leslie Krause

3

Lua là ngôn ngữ kịch bản nhẹ mà muốn nhỏ hơn có thể. Ví dụ: nhiều thao tác đơn phương như gia tăng trước / sau không khả dụng

Thay vì tiếp tục, bạn có thể sử dụng goto như

arr = {1,2,3,45,6,7,8}
for key,val in ipairs(arr) do
  if val > 6 then
     goto skip_to_next
  end
     # perform some calculation
  ::skip_to_next::
end

-3

Tại sao không có tiếp tục?

Bởi vì nó không cần thiết¹. Có rất ít tình huống mà một nhà phát triển sẽ cần nó.

A) Khi bạn có một vòng lặp rất đơn giản, hãy nói 1 hoặc 2 lớp, sau đó bạn có thể chỉ cần xoay điều kiện vòng lặp và nó vẫn có thể đọc được.

B) Khi bạn viết mã thủ tục đơn giản (hay còn gọi là cách chúng tôi viết mã trong thế kỷ trước), bạn cũng nên áp dụng lập trình có cấu trúc (hay còn gọi là cách chúng tôi viết mã tốt hơn trong thế kỷ trước)

C) Nếu bạn đang viết mã hướng đối tượng, thân vòng lặp của bạn sẽ bao gồm không quá một hoặc hai lệnh gọi phương thức trừ khi nó có thể được biểu thị trong một hoặc hai lớp (trong trường hợp này, xem A)

D) Nếu bạn đang viết mã chức năng, chỉ cần trả về một cuộc gọi đuôi đơn giản cho lần lặp tiếp theo.

Trường hợp duy nhất khi bạn muốn sử dụng một continuetừ khóa là nếu bạn muốn mã Lua giống như nó là python, mà nó không phải là .²

Có cách giải quyết nào cho nó?

Trừ khi A) áp dụng, trong trường hợp không cần bất kỳ cách giải quyết nào, bạn nên thực hiện lập trình Cấu trúc, Hướng đối tượng hoặc Chức năng. Đó là những mô hình mà Lua được xây dựng cho, vì vậy bạn sẽ chiến đấu chống lại ngôn ngữ nếu bạn tránh đường để tránh những khuôn mẫu của chúng .³


Một số làm rõ:

Lua là một ngôn ngữ rất tối giản. Nó cố gắng có ít tính năng nhất có thể, và một continuetuyên bố không phải là một tính năng thiết yếu theo nghĩa đó.

Tôi nghĩ rằng triết lý tối giản này được nắm bắt tốt bởi Roberto Ierusalimschy trong cuộc phỏng vấn năm 2019 này :

thêm điều đó và điều đó và điều đó, đưa nó ra, và cuối cùng chúng tôi hiểu kết luận cuối cùng sẽ không làm hài lòng hầu hết mọi người và chúng tôi sẽ không đưa ra tất cả các tùy chọn mà mọi người muốn, vì vậy chúng tôi không đưa ra bất cứ điều gì. Cuối cùng, chế độ nghiêm ngặt là một sự thỏa hiệp hợp lý.

² Dường như có rất nhiều lập trình viên đến Lua từ các ngôn ngữ khác bởi vì bất kỳ chương trình nào họ đang cố gắng viết kịch bản để sử dụng nó, và nhiều người trong số họ muốn dường như không muốn viết bất cứ điều gì ngoài ngôn ngữ của họ sự lựa chọn, dẫn đến nhiều câu hỏi như "Tại sao Lua không có tính năng X?"

Matz đã mô tả một tình huống tương tự với Ruby trong một cuộc phỏng vấn gần đây :

Câu hỏi phổ biến nhất là: "Tôi đến từ cộng đồng ngôn ngữ X, bạn không thể giới thiệu một tính năng từ ngôn ngữ X sang Ruby?", Hoặc đại loại như thế. Và câu trả lời thông thường của tôi cho những yêu cầu này là "không, tôi sẽ không làm điều đó", bởi vì chúng tôi có thiết kế ngôn ngữ khác nhau và các chính sách phát triển ngôn ngữ khác nhau.

Có một số cách để hack theo cách này; Một số người dùng đã đề xuất sử dụng goto, đó là một sự ước lượng đủ tốt trong hầu hết các trường hợp, nhưng lại rất xấu xí rất nhanh và phá vỡ hoàn toàn với các vòng lặp lồng nhau. Việc sử dụng gotocũng khiến bạn gặp nguy hiểm khi có một bản sao SICP ném vào bạn bất cứ khi nào bạn hiển thị mã của mình cho bất kỳ ai khác.


1
Tôi đã từ chối vì câu đầu tiên rõ ràng là sai và phần còn lại của câu trả lời là không có ích.
bfontaine

Vô ích? Có lẽ; đó là một câu trả lời dựa trên ý kiến. Câu đầu tiên rõ ràng là đúng mặc dù; continuecó thể là một tính năng tiện lợi, nhưng điều đó không cần thiết . Rất nhiều người sử dụng Lua tốt mà không có nó, vì vậy thực sự không có trường hợp nào khác ngoài tính năng gọn gàng không cần thiết cho bất kỳ Ngôn ngữ lập trình nào.
DarkWiiPlayer

Đó không phải là một cuộc tranh cãi: bạn không thể tranh luận rằng mọi người sẽ "ổn nếu không có nó" khi họ không có lựa chọn nào khác.
bfontaine

Tôi nghĩ rằng chúng ta chỉ có định nghĩa khác nhau về "cần thiết" sau đó.
DarkWiiPlayer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.