Set one element in array

Hello,

I tried to change one element in an array, but in fact it did not change.

set :q, [1,2,3,4,5,6,7,8,9,0]
print get[:q] # -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
Is ok!

set :q[3], 0
print get[:q] # -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

q[3] expected to be 0, but is still 4! Why?

Thanks, Ruber.

I think you’ld have to do it something like this:

set :q, [1,2,3,4,5,6,7,8,9,0]
print get[:q]
k=get[:q]
set :q,k[0..2]+[0]+k[4..-1]
puts get[:q] # =>[1, 2, 3, 0, 5, 6, 7, 8, 9, 0]

(edited this as code insert as the original post changed two dots . . into … )

Hi there,

set and get were designed to work across threads. For this to work correctly (i.e. not introduce non-deterministic behaviour) you can only store immutable data using set. As Ruby arrays are mutable, Sonic Pi will deep-freeze them automatically (throwing an error if this isn’t possible).

The core characteristic of immutable data structures is that they can’t be changed once created. This is explained in greater detail in Section 10 of the built-in tutorial. However, whilst rings are also immutable, they support efficient modification which returns a new separate ring leaving the original intact. This allows you to model mutability whilst keeping a system which works well across threads. You can create a new ring based of a modification of an existing ring using the .put method which takes two arguments, the index to put something into and the value to put in it (the value must also be immutable). Here’s an example:

set :q, (ring 1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
print get[:q]

old_q = get[:q]
puts old_q.frozen?

new_q = old_q.put(4, 0)

assert_equal old_q, (ring 1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
assert_equal new_q, (ring 1, 2, 3, 4, 0, 6, 7, 8, 9, 0)

set :q, new_q

Hope that this helps,

Sam

Also, I should add that @robin.newman’s solution also works because he isn’t modifying the array, rather using the range operator (..) and concatenation (+) to construct a new array :slight_smile:

Hello

q1=[0,1,2,3,4] # original
puts q1 # => [0, 1, 2, 3, 4]
q2=q1 # copy
puts q2 # => [0, 1, 2, 3, 4]
q1=[-2,-1]+q2[0,3]+q2 # original modified
puts q1 # => [-2, -1, 0, 1, 2, 0, 1, 2, 3, 4]
q1[3]=150 # Change element 4
puts q1 # => [-2, -1, 0, 150, 2, 0, 1, 2, 3, 4]

{run: 6, time: 0.0}
├─ [0, 1, 2, 3, 4]
├─ [0, 1, 2, 3, 4]
├─ [-2, -1, 0, 1, 2, 0, 1, 2, 3, 4]
└─ [-2, -1, 0, 150, 2, 0, 1, 2, 3, 4]

Hello,

Thank you for your replies, background information, explanations and solutions.

After looking at the suggested solutions, I have made a new simplified solution:

set :q, [1,2,3,4,5,6,7,8,9,0]
print get[:q] # -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

To change the 4th element to 0:

set :q, get[:q][0…2]+[0]+get[:q][4…-1]
print get[:q] # -> [1, 2, 3, 0, 5, 6, 7, 8, 9, 0]

Now I am curious if this can be seen as coding in a bad behaviour?

At this point I like to take the opportunity to thank en compliment Sam, and his team,
for creating Sonic Pi. It is not only fun, but a lot of serious fun using Sonic Pi!
I really do hope further development will be made possible.

Thank you, Ruber.

Hi -

Now I am curious if this can be seen as coding in a bad behaviour?

TL;DR + IMHO + AFAIK: You’re fine, because SonicPi is about play. But at some point you might become frustrated by unexpected behavior. Until then, however, play around - because you might also become happily surprised by the unexpected behavior!

Details:

So, if I read the SonicPi docs correctly, then it’s not that SonicPi prevents race conditions, it’s that it makes them repeatable; so I’d say the answer to your question is “sort of”.

Mutating (changing) global state like you want to do means that you, as the programmer, cannot be sure what that state is at any point during your program, and thus can’t be sure what your program will do, overall. This is part of what it means to have “race conditions” in your code.

However, in SonicPi, this is (AFAIK) fine, and since SonicPi handles race conditions by making them repeatable - in other words, although you as the programmer cannot know what will happen, you can just try it, and if you like the result, it can keep happening.

To put it another way - Race conditions mean that each time you run your code, something different might happen, without you having changed anything. In SonicPi, however, unless you change your code, the same thing will definitely happen each time you run your code.

However however, you may run into an issue where you like what’s happening, but in the course of changing the code, you might affect the timing between what’s updating q and what’s reading q, and thus change what happens. This is where you might be frustrated, and if you get here, you would then want to consider other ways of doing what you’re trying to do with q.

Actually, calling get and set is also deterministic and will always do the same thing regardless of multiple threads. However, if you add more threads or remove threads which are calling set on the same key, behaviour might change (although it will always be the same with the same threads and same code). You should therefore only use get and set to coordinate values across threads. If you’re doing things locally within a thread/live_loop you might be wise to stick to standard variables (until I’ve implemented a thread-local variant of get and set :slight_smile: )

Oh, wow! That’s really impressive. How’d you pull that off?

Science!!! :wink:

Eli…

The implementation is still extremely nascent, but the general idea is to use Sonic Pi’s timing model to total order all writes and then to block reads until it is known that no other thread can write in a way that will modify the read (i.e. all threads promise not to write a certain amount of time in the past).