lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]



On 24-Jun-05, at 6:25 AM, Customer Support wrote:

On Wed, 22 Jun 2005 - Rici Lake wrote:

"So, if you could provide me with a sample of your Lua code, I might have a
bit more confidence in your certification program."

I'm sorry that I can't fulfill this request directly. The Lua code that is sufficiently complex so that it would be of interest to you is also a trade
secret for us.  (Since it is used in our business operations.)

Ooh, trade secrets. Good thing I don't have any of those. My code is scattered about all over the place. :)

Actually, I would have been interested in the solution to any reasonably standard programming problem.

As a
somewhat lesser substitute, here is a question from one of our practice
exams:

This exercise strikes me more as a "I know how to move the pieces" puzzle than an illustration of programming. Clearly, line 29 would create an error since coroutine.status requires a thread as an argument rather than nil, which is what you're providing. However, it's never reached because of the endless loop in the thread created by cons(). I suppose that means that the answer is (d), 'there is no output', although that's not strictly speaking true: eventually something will happen to terminate the loop, such as an irate sysadmin killing the process, and you'll receive some sort of notification of that. I don't know what that proves.

A more useful question would have been: How do you fix the bug(s) in the following code? But that would require an explanation of what the code was supposed to do (hard to answer, I would think) and it's not the sort of question that leads itself to multiple-choice answers.

Anyway, since you provided me with an estimated US$1.00 of sample questions, I've returned with my S/0.07 at today's exchange rate:

Given the following code:

1	x = 0
2
3	prod = coroutine.create(
4	function()
5	    while true do
6	        put(x)
7	        x = x + 2
8	    end
9	end)

The use of a global variable here is very bad style. Clearly, x is intended to be private to prod, and there is not reason to pollute the global namespace with it. Usually one would do this with a factory function:

-- The argument to Producer is the initial value
function Producer(x)
  return coroutine.create(
    function()
      while true do
        put(x)
        x = x + 2
      end
    end)
end

Producer() is not a good name for this function, since it says nothing about what it does. But what does it do?

First, it's clear that put() must at some point call coroutine.yield() for this to do anything useful at all; otherwise it is simply an infinite loop. put() might transform x or it might filter x, or both, but it must eventually call yield(). In any event, one has to ask what the point of using a global variable for this function is: either it is simply coroutine.yield(), in which case it would have been clearer just to say so, or it is effectively a parameter to the factory. And even if it were, it seems an unnecessary complication to force put to do something (yield) which we clearly know how to do.

In the event, we have:

18	function put(x)
19	    coroutine.yield(x)
20	end

Which is a classic Lua beginner's error. Why add an extra function wrapper? This should have been written:

  put = coroutine.yield

or, better:

  local put = coroutine.yield

Since its primary purpose is presumably that the programmer prefers the word 'put' to the word 'yield'.

But returning to the theme, let's try this:

-- Returns a coroutine which yields those values from the infinite sequence -- [init, init+incr, init+2*incr...] for which filter(x) returns a true value. function FilteredStep(filter, init, incr)
  return coroutine.create(
    function()
      -- minor efficiencies assuming this will run a lot
      local filter, x = filter, init
      local yield = coroutine.yield
      while true do
        if filter(x) then yield(x) end
        x = x + incr
      end
    end
  )
end

One might, of course, prefer the use of coroutine.wrap to return a more easily callable result, as we discover almost immediately on trying to use it. We might then want a little wrapper, similar to the one in your example:

22	function get(p)
23	    local status, value = coroutine.resume(p)
24	    return value
25	end

But that wrapper has a number of problems. First of all, it simply tosses away the error if there was one. Second, it also tosses away any return values other than the first one. So we could try to do better:

local function a(...) return arg end
function get(p)
  local results = a(coroutine.resume(p))
  if a[1] then
    table.remove(a, 1)
    return unpack(a)
  else
    error(a[2], 0)
  end
end

However, all we have done there is redefine coroutine.wrap() inefficiently in pure Lua. So we might as well use it in the first place.

So, let's give all that a whirl:

-- Returns a wrapped coroutine which yields those values from the infinite sequence -- [init, init+incr, init+2*incr...] for which filter(x) returns a true value.
function FilteredStep(filter, init, incr)
  return coroutine.wrap(
    function()
      -- minor efficiencies assuming this will run a lot
      local filter, x = filter, init
      local yield = coroutine.yield
      while true do
        if filter(x) then yield(x) end
        x = x + incr
      end
    end
  )
end

function test(k)
  local function askuser(x)
    io.write(string.format("Do you like the number %g? ", x))
    return (string.find(io.read('*l'), '^%s*[yY]'))
  end

  local evens = FilteredStep(askuser, 2, 2)
  local negatives = FilteredStep(askuser, -1, -2.5)
  local liked = {}
  for i = 1, k do
    table.insert(liked, (evens()))
    table.insert(liked, (negatives()))
  end
  table.sort(liked)
  print("\nYou say you like: "..table.concat(liked, ', '))
end

Before we run this program, can you predict its behaviour?
  What is the significance of k?
  What order are we going to be asked?
  Do coroutines actually help reveal the logic?
  What might you do to make the program more user-friendly?
  What elementary error have I committed relating to the
    handling of stdin in the standalone interpreter?


OK, here we go

> test(3)
Do you like the number 2? yes
Do you like the number -1? yes
Do you like the number 4? no
Do you like the number 6? yes
Do you like the number -3.5? no
Do you like the number -6? not much
Do you like the number -8.5? maybe
Do you like the number -11? yes, ok then
Do you like the number 8? yes
Do you like the number -13.5? no
Do you like the number -16? yep

You say you like: -16, -11, -1, 2, 6, 8