Why does trying to display local variables in REPL always lead to errors or stange things? I am talking about non-function ones. By the way i if someone could explain to me the advantage of making all my global variables local

Lua: odd table copying behavior

I’m really a novice to Lua and programming, so I guess the following is just a “pilot’s error”.

I want to create a function to “rotate” values in a table, so that I can turn {1, 2, 3, 4} into {4, 1, 2, 3}. I guess there are several ways to accomplish this, but the one I chose involves creating a copy (called copy, surprisingly) of the table original whose content I want to rotate, and then run this code:

  for i = 1, #original do
    if i==1 then
      original[i]=copy[#original]
    else
      original[i]=copy[i-1]
    end

When I do the table copy via this code,

for i = 1, #original do
copy[i] = original[i]
end

everything works as expected, and {1, 2, 3, 4} becomes {4, 1, 2, 3}.

But when I use this shorter bit of code to do the copy action,

copy = original

all of a sudden {1, 2, 3, 4} becomes {4, 4, 4, 4}, which is unusable. At first I thought that the shorter copy action itself is wrong, but interestingly, immediately after this copy action both copy and original contain the same values at the same positions.

Please, can somebody be so kind to explain what is causing this odd behavior?

assigning a table to a variable

copy = original

just puts a reference to original in copy - NOT a copy so you now have two variables pointing at the same table.

Frankly if you want to rotate a table I’d just insert the last element at the front of the queue and then remove the last element - two steps + knowing the length of the table

a = {1,2,3,4}
table.insert(a,1,a[#a])
table.remove(a,#a)

and if you need a copy then you can copy it (or if you want a copy of the original do that first and then rotate the other table)

Copying has unexpected traps - what if one of the elements is a table etc. etc and as ever simple dumb code is preferable to complex code. Obviously my code isn’t thread safe because at one point the table has an extra unwanted element but I think lua only has co-operative multithreading so that shouldn’t be an issue

2 Likes

locals can only be accessed in their own scope. (the chunk, file, or function that defined them.) the REPL has its own scope and in fact its own environment which shadows the global environment. so the downside of using local in scripts is that you simply can’t see those variables in the REPL.

the upside of using locals is that it is bad to pollute the global namespace from your script. specifically it can lead to two pitfalls, of which we’ve already seen plenty: 1) you can easily clobber vital global vars used by the nonrns system itself, possibly preventing norns from starting if you do this in the default script. 2) you can easily accidentally use globals defined in previously run scripts, leading to weird bugs and indeterminate behavior.

we’ve discussed sandboxing of scripts to make both these issues go away, and clear a path towards inspecting script variables from the REPL. see this issue:
https://github.com/monome/norns/issues/425
and this PR:
https://github.com/monome/norns/pull/436
and elsewhere.

we just need to commit to a solution and then revise all the existing scripts.

5 Likes

Some questions regarding Parameter Sets (as discussed in norns study 3):
I do have several tables in my script, whose values I’d like to store for later recall. From what I understood in study 3, in order to store a value in a parameter set, I first need to define a parameter (add_number) to which I assign the value. Does this mean that if I want to store a table, I do need to define a parameter for each value in the table? Or can a parameter be a table? If yes, how do the minimum/maximum/default values of the parameter definition work for a table?

I should mention that I am a complete novice at programming, and all that I am currently doing is trying to understand the AWAKE script, and modifying it step by step so that it does more of what I want…and I am really enjoying this feeling of embarking onto a new journey!

that’s one possible way of storing/recalling tables, but depending on the table and its content it may not be the most optimal one. what are you storing in your table?

no.

you might want to look at the playfair script which persists table content in a playfair.data file.

also read about table persistence in lua in general at https://www.lua.org/manual/2.1/subsection3_7_3.html.

i’m quite new to lua aswell.

imo, utility methods could be added to the norns framework to support these kinds of operations.

1 Like

@wolfgangschaltung FYI @jah is referring to the new version of playfair which is on github, not on release yet:

see the functions at the very bottom

2 Likes

@jah & @tehn: Thanks for your answers, these gave me quite something to chew on.

There are 2 tables (with integer numbers), and 4 integer numbers, the totality of which I want to store and recall as a preset (while the AWAKE sequence is running, without messing with the timing).

I understand that such a preset can be accomplished with nested tables inside the script, but when I send norns to sleep, everything will be gone, so I must find a way to write the contents of all presets I might create at runtime to disk to retrieve them later (e.g. after awakening norns).

Thanks, but I have to admit this is way over my head (I am not only new to Lua, but new programming at all). To name just 2 questions I had after reading it:

  • where do I specify the name for the text file that is generated?
  • how do I load the values stored in the text file? The text just said “In order to restore this value, a lua_dofile suffices.”, which left me rather puzzled.

Thanks much, while I am far from really understanding it, this looks way more explicit and thus comprehensible to me than the section from the Lua manual! I will try to work my way thru this during the next days, for now I will try to create the (volatile) preset management in AWAKE first.

Just 2 questions for now:

  • The code for the new PLAYFAIR ends with
cleanup = function()
  playfair_save()
  end

What does this mean? cleanup is not used anywhere else in the script, so when&how do these lines get executed?

  • What is the purpose of these require commands, and why are they sometimes put into variables?

require 'er'

engine.name = 'Ack'

local ack = require 'jah/ack'
local BeatClock = require 'beatclock'

Thanks much in advance!

by default, numbers in lua are represented as double-precision floating point (64 bits). so max value is approximately 1.8 x 10308.

with lua 5.3, you can also explicitly create variables of integer type using foo = int(555). this also uses 64 bits, giving the range [-9223372036854775808, 9223372036854775807].

for example

local foo = require 'foo'
foo.dosomething()

this is the recommended way to use code modules in lua (aka libraries.) require 'foo' looks for a module source named foo.lua. the assumption in this idiom is that foo.lua returns a table (here containing the dosomething() method.) if foo has previously been loaded by require, it returns a reference to this table; otherwise it executes foo.lua.

local foo = require 'foo' stores a local reference to foo. this is safer than polluting the global namespace for various reasons, and also allows you to name the local reference whatever you want. f = require 'foo'; f.dosomething() is also valid (in case you want to use the name foo for something else, or don’t want to type it, or whatever.)

more on modules in lua: http://lua-users.org/wiki/ModulesTutorial

if your script defines cleanup(), it will be executed by the script loading module when the next script is loaded.

[mod: i’m moving this to Norns:scripting]

3 Likes

Thanks much for the explanations, also for the link to lua-users.org!

I remember I read about it in the reference manual some days ago:
“Tables, functions, threads, and (full) userdata values are objects: variables do not actually contain these values, only references to them.”, but obviously I did not understand the consequences. Thanks much, I get it now!

Thanks for pointing me to this trick…another example where I forgot what I have read just a couple of days ago in norns study #2. I blame it on the excessive heat here in Germany :wink:

1 Like

Reading up the thread you are new to all this I see. Reference and value is easy to trip up on early on (& another good reason to stay away from copying things - it quickly gets you into language depths) when I start using a new language it’s something I look up very quickly.

The best advice I can give you from a life time of coding (nigh on 40 years - eek!) is don’t try and do fancy things. Go for straight forwards and obvious. And especially don’t take short cuts. So in this case copying and rotating are two separate things. So do them separately. I know the temptation is to think “oh I can do that in one step”. Resist. Future you will like now you much more :wink:

4 Likes

Thanks, I will try to follow your advice!

If I may ask one more question: With what you said in mind, what’s the recommended way when all I want to do is to make a 1:1 copy of a table into a different table, and all elements are numbers? From what I know right now, I’d probably still go with

for i = 1, #original do
copy[i] = original[i]
end

As long as the elements are always simple the you are good with that code (& in fact you can see in my recent Contribution to foulplay.lua in dust I made the exact same choice for the same reason)

Assumptions are what catch you out so consider making that assumption explicit in a comment but as long as it is you and your code making the table in the first place I wouldn’t worry

1 Like

Ok thank you, those are very useful informations.

as it happens, @Justmat just landed rotation for foulplay

https://github.com/monome/dust/pull/163

might be a handy jumping off point? (and maybe something we should consider moving to a shared library.)

1 Like

I had considered putting the rotate_pattern() function in lib/lua/er.lua, but wasn’t sure. :sweat_smile:

1 Like

:+1:

if/when you want to copy more than just numbers, you might find these reflections useful: http://lua-users.org/wiki/CopyTable

1 Like

Yeah, @Justmat and I talked about this when I hacked together that rotate function.

Would we want to throw a github pull request up for adding utility functions like this to the libraries?

3 Likes

+1 for (documented) additions to the library - much better to have single implementations of things that are well used (and therefore robust) and unless this kind of stuff goes in early you end up with all kinds of cut and paste or totally separate versions scattered throughout scripts…

1 Like