Search code examples
lualanguage-design

What is 'Should the conditional mechanism be an integral part of the exit' means when it comes the language design?


I had an assignment for my Programming Language homework which makes us to experiment over the language Lua. One of the aspect of the language we required to experiment over is should the conditional mechanism be an integral part of the exit (conditional or unconditional exit)? So my friends and I discussed it as it is a question of whether the loop construction of the program needs controller or not. For example in Lua

for i = 0 , 5, 1 do
   print("Something")
end

loop statement chooses conditional exit according to our discussion with friends. On the other hand, I think it is not a design choice since the generally every programming language chooses conditional exit. So why it is a design choice for the programming language? If we think wrong about design decision can you explain what is this design decision about?


Solution

  • Question

    I will assume you're referring to the following notes (based on a quick search):

    It is sometimes convenient for a programmer to choose a location for loop control other than the top or bottom of the loop. Design issues:

    • Should the conditional mechanism be an integral part of the exit?
    • Should only one control body be exited, or can enclosing loops also be exited? C and C++ have unconditional unlabeled exits (break) Java, Perl, and C# have unconditional labeled exits (break in Java and C#, last in Perl) [...]

    - https://www2.southeastern.edu/Academics/Faculty/kyang/2016/Fall/CMPS401/ClassNotes/CMPS401ClassNotesChap08.pdf

    Loops

    Lua offers 5 looping constructs: The classic while loop for loops that are supposed to run zero or more times, the repeat-until loop (comparable to do-while, except the condition is inverted) for loops that run one or more times, the numeric for var = from, to, step loop from your example for iterating over evenly spaced numbers in a range known beforehand, the very flexible generic for var1, var2, ... in iterator, invariant_state, control_var loop, and finally, tail recursion using function f() --[[body]] return f() end.

    break

    Tail recursion is the odd one out here so let's start with the other loops. Given a while/repeat or for loop, you can use the break loop control statement to exit the innermost loop. You could use this to implement for i = 1, 7 do print(i) end as follows without using a loop condition:

    local i = 1
    while true do
        print(i)
        if i == 7 then
            break
        end
        i = i + 1
    end
    

    (note that this is not a particularly useful example).

    Breaking multiple loops

    To exit multiple loops, you'd have to use either a goto or a return:

    for i = 1, 10 do
        for j = 1, 10 do
            if frobnicate(i, j) then
                goto after_loops
            end
        end
    end
    ::after_loops::
    ...
    

    using a return:

    (function()
        for i = 1, 10 do
            for j = 1, 10 do
                if frobnicate(i, j) then
                    return
                end
            end
        end
    end)()
    ...
    

    (often this would lead you to properly extract the nested loop into a named function)

    Other workarounds use boolean variables stuffed into the loop conditions/iterators to achieve the same effect, but this is rather dirty (and also slightly inefficient). goto is the cleanest solution here.

    continue

    Lua does not have a continue. The simplest workaround again is to wrap the chunk you want to continue in a function and call return:

    for k1, v1 in pairs(t1) do
        for k2, v2 in pairs(t2) do
            (function()
                 if foo(k1, v2) then
                     return -- "continue"
                 end
                 bar(k2, v1)
                 foobar(k1, v1)
            end)()
        end
    end
    

    Caveat: You're creating an anonymous function in every loop iteration. It is again flexible enough to allow continuing multiple loops. Again, a goto is the cleaner solution:

    for k1, v1 in pairs(t1) do
        for k2, v2 in pairs(t2) do
            if foo(k1, v2) then
                goto continue
            end
            bar(k2, v1)
            foobar(k1, v1)
            ::continue::
        end
    end
    

    Finally, if you use (tail-)recursive functions and want to jump out of multiple functions at once, you can use coroutine.yield for that; in fact, coroutine.wrap is usually used for complex (recursive) iterators, as you'd otherwise have to explicitly manage the program state (stack) iteratively rather than managing it implicitly in the call stack.

    Design decisions involved

    • Lua provides plenty of looping (condition at begin/end, numeric for, generic for) constructs (excepting tail recursion); usually one of the 4 suits the task well.
    • Lua provides a break statement for exiting a single loop.
    • Lua does not provide a continue statement.
    • Lua provides a powerful goto which can be used to the same effect as breaking/continuing of labeled loops in other programming languages. This serves to eliminate the need for continue at slight verbosity.
    • Lua has powerful closures with tail recursion support which can be used to work around limitations. Tail recursion is the first and foremost looping construct of Lisp-family functional languages; rather than having to explicitly break the loop, you can start the next loop iteration (function call) using return func(...) at arbitrary places, skipping the rest of the function body. If you use upvalues, you don't even have to pass parameters.