This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
25hz's profile picture

Hot To Streamline Code: An Arg With A Loop

Started by 25hz, 22 October 2013 - 09:16 PM
25hz #1
Posted 22 October 2013 - 11:16 PM
Is there some way to streamline this code to combine the argument with the "while" line? This works great, but I'd like to incorporate it with some other functions, and was wondering if there was a way to tighten it up?


function f() -- move forward, dig if necessary
arg = (...)
arg = tonumber(arg) or 1
x=0
   while x < arg do
      if turtle.forward() == true then
         x=x+1
         else
         turtle.dig()
      end
   end
end

Thanks for any advice.
distantcam #2
Posted 23 October 2013 - 01:24 AM
You could evaluate the arg in the while, but you don't want to because that evaluation would run every time the while goes around.

The only thing I would change is the if

if turtle.forward() then
theoriginalbit #3
Posted 23 October 2013 - 01:28 AM
Well firstly that won't work, It'll error along the lines of "cannot use … outside of vararg function" or something to that tune.

However allowing for your typo this would be my first suggestion

local function f(...) --# localise the function
  local arg = tonumber((...)) or 1 --# localise the variables
  local x = 0
  while x < arg do
	if turtle.forward() then --# no need for == true, the function returns a boolean no need to do true == true, just use the returned boolean
	  x = x + 1
	else
	  turtle.dig()
	end
  end
end
But this can be further improved by not using a while loop, but instead using a for loop. A for loop will cycle from the first specified number through to the second, at the increment specified by the optional third (when absent +1 is assumed) example loops:

--# basic increment
for i = 1, 10 do
  print("Ten times ", i)
end
--# changed increment
for i = 1, 10, 2 do
  print("Five times ", i)
end
--# decrementing
for i = 10, 1, -1 do
  print("Ten times ", i)
end
--# would not run as 15 is > than 10
for i = 15, 10 do
  print("You don't see me")
end
so therefore we can change you loop to

local function f(...)
  local arg = tonumber((...)) or 1
  for i = 1, arg do
	--# this change is needed so that it will try to move forward until it does
	while not turtle.forward() do --# the not keyword inverts the result so not true = false and not false = true
	  turtle.dig()
	end
  end
end
This code is starting to come along quite nicely, however there is one more change that can be made, and that is to specify an argument name as opposed to using a vararg, since we only want one argument not an unspecified amount, so we can then do

local function f(times)
  times = tonumber(times) or 1
  for i = 1, times do
	while not turtle.forward() do
	  turtle.dig()
	end
  end
end
Now as distantcam stated while I was typing this up, you could put the evaluation inside the conditional, but don't let anyone tell you to do this, ever, it is inefficient as you're having to make that evaluation/conversion each time it loops, as opposed to just once.

Hope all this helped and makes sense

— BIT

EDIT: Oh also incase you're interested, the reason you should use local variables and functions is
It is good programming style to use local variables whenever possible. Local variables help you avoid cluttering the global environment with unnecessary names. Moreover, the access to local variables is faster than to global ones.

Reference
Also as for the name of your function, I do suggest that you get in a habit of better naming conventions like calling it multiForward, or even forward, instead of just f it can make the world of difference later on down the track when you're trying to debug something, or if someone else is reading your code.
Edited on 22 October 2013 - 11:33 PM
25hz #4
Posted 23 October 2013 - 04:57 PM
Well firstly that won't work, It'll error along the lines of "cannot use … outside of vararg function" or something to that tune.

However allowing for your typo this would be my first suggestion

local function f(...) --# localise the function
  local arg = tonumber((...)) or 1 --# localise the variables
  local x = 0
  while x < arg do
	if turtle.forward() then --# no need for == true, the function returns a boolean no need to do true == true, just use the returned boolean
	  x = x + 1
	else
	  turtle.dig()
	end
  end
end
But this can be further improved by not using a while loop, but instead using a for loop. A for loop will cycle from the first specified number through to the second, at the increment specified by the optional third (when absent +1 is assumed) example loops:

--# basic increment
for i = 1, 10 do
  print("Ten times ", i)
end
--# changed increment
for i = 1, 10, 2 do
  print("Five times ", i)
end
--# decrementing
for i = 10, 1, -1 do
  print("Ten times ", i)
end
--# would not run as 15 is > than 10
for i = 15, 10 do
  print("You don't see me")
end
so therefore we can change you loop to

local function f(...)
  local arg = tonumber((...)) or 1
  for i = 1, arg do
	--# this change is needed so that it will try to move forward until it does
	while not turtle.forward() do --# the not keyword inverts the result so not true = false and not false = true
	  turtle.dig()
	end
  end
end
This code is starting to come along quite nicely, however there is one more change that can be made, and that is to specify an argument name as opposed to using a vararg, since we only want one argument not an unspecified amount, so we can then do

local function f(times)
  times = tonumber(times) or 1
  for i = 1, times do
	while not turtle.forward() do
	  turtle.dig()
	end
  end
end
Now as distantcam stated while I was typing this up, you could put the evaluation inside the conditional, but don't let anyone tell you to do this, ever, it is inefficient as you're having to make that evaluation/conversion each time it loops, as opposed to just once.

Hope all this helped and makes sense

— BIT

EDIT: Oh also incase you're interested, the reason you should use local variables and functions is
It is good programming style to use local variables whenever possible. Local variables help you avoid cluttering the global environment with unnecessary names. Moreover, the access to local variables is faster than to global ones.

Reference
Also as for the name of your function, I do suggest that you get in a habit of better naming conventions like calling it multiForward, or even forward, instead of just f it can make the world of difference later on down the track when you're trying to debug something, or if someone else is reading your code.

Thanks for the replies :)/> I appreciate it.

Thanks for the help on the loop. That shortens it down by a couple lines and gives it a specific value. Both, good things I can take advantage of.

I didn't use "local" functions because I misunderstood them when I read about them. I thought "local" was local to the function, not the script, and I thought non-local parameters were local to the script/program, and not to the whole environment. I'll go back and do a block change and add local to all the parameters and functions. I understand how I was causing a lot of clutter in the environment by not using "local". I imagine at times that clutter might cause unforcasted results when a command is pulled from the environment, when you didn't know it was there. I assume every one of those non local parameters goes toward the 256 item total that I read about for the memory stack, or something like that?

Also, I used single letter names for the functions because it saves typing space and time. I comment the hell out of all my scripts so each function and usually parameter too, has a description of what it is doing, including the individual sections that are executing commands. I used the short function names because I am writing a turtle script that builds a house. In another thread, I posted about how I'm having trouble with an inventory sorting function. I would love to be able to write a function that would build an entire wall, or layer of a floor, and then I could and would use more legible names. Because I can't get a sorting function to work, I have to do a lot of the sections manually, and manually select a new slot when the current slot's blocks run out. That's a lot of typing, so I try to minimize it where I can. There are some sections where I can and do call a bunch of functions from a single "if" statement, and then run it multiple times, but only when it doesn't require a new stack being selected. Once I get a sorting script working I'll probably be able to cut 1000 lines down to 500. I can use longer names then too :)/>
jay5476 #5
Posted 23 October 2013 - 06:40 PM
i dont think you quite get locals see this

local x = 1
print(x) -- prints 1
do
local x = 5
print(x) -- prints 5
end
print(x) -- prints 1
local can be local to functions, do blocks and scripts, not just scripts
theoriginalbit #6
Posted 23 October 2013 - 11:53 PM
I didn't use "local" functions because I misunderstood them when I read about them. I thought "local" was local to the function, not the script, and I thought non-local parameters were local to the script/program, and not to the whole environment. I'll go back and do a block change and add local to all the parameters and functions. I understand how I was causing a lot of clutter in the environment by not using "local". I imagine at times that clutter might cause unforcasted results when a command is pulled from the environment, when you didn't know it was there.
Yeh local does indeed mean localised to the code block it is within and any sub blocks, so a local variable inside the body of the program can be used by any other variable of function. A local variable inside a function can only be used within that function, or any other function/variable defined within it. Definitely having everything in the environment can be bad, not only as that link stated is a local variable quicker to access, but when you have too many global variables you can start to have naming collisions which means variables can start being different or unexpected values, whereas with localised variables, scope takes care of the naming collision and the one in the "closest" block is the one that's used, not any other variable.

I assume every one of those non local parameters goes toward the 256 item total that I read about for the memory stack, or something like that?
Actually no, and honestly would be quite annoying if it did :P/> the stack that has the limit of 256 is actually the function call stack, so in programming languages each time you're in a function, the function is sitting on a call stack, and it has an associated heap storing all its variables (the call stack can also do this), the call stack also keeps track of a bunch of other things including where it is currently up to within a function. This means that if you call another function, it will go off, do that function, and once it completes return perfectly to where the function was up to, still with all the variables intact and how they were previously (unless explicitly changed by the invoked function). This function call stack is what will overflow when too many function calls have been made. So a visual example

--# at this point there would be 1 "function" on the call stack, our program
local function bar()
  --# there is now another function on the call stack, with a count of 3
  print("Hello world!") --# print is invoked, and added to the call stack, there's now 4 elements
  --# `print` has finished, call stack is now again 3 elements
end

local function foo()
  --# there are now 2 elements on the call stack
  bar() --# `bar` gets invoked
  --# `bar` has finished, there is now 2 elements again
end

foo() --# here another function `foo` gets added to the function call stack
--# `foo` has been removed from the call stack, there is now only the main program on the call stack
Now for a normal CC program its very difficult to get even remotely close to filling the call stack even with the most advanced of programs (closest I've got was 179 in CCTube) until you use recursion

local foo
function foo()
  foo()
end

foo()
or cyclic-recursion

local foo, bar

function foo()
  bar()
end

function bar()
  foo()
end

foo()
with these two types of calls you can very quickly fill the call stack, as such recursion should be avoided at all costs, and there are normally better solutions anyway such as loops. However from time-to-time there are problems that cannot be solved except with recursion, but you always make sure the recursion will end, there is also another solution which is advised only to use when recursion IS needed, and its tail-calls

local foo
function foo()
  return foo() --# this current function is finished, and then foo is called, meaning the call stack does not increase (or decrease) in size
end
foo()

-snip-
Dude, indentation, you should know better by now!
jay5476 #7
Posted 24 October 2013 - 12:05 AM
-snip-
-snip-
Dude, indentation, you should know better by now!
yes i do know better but shameful me was to lazy to press space 4 times on my iPod