For part 2 of today’s (day 17) of Advent of Code, although there’s a much smarter way to do it, I replaced some slow Array-backed code with fast-insert code using a circular list. Which required mutation. Which resulted in a bug: my initial test code gave correct answers, but subsequent uses of this code started to lie.
…
I’m giving you this time so that you can emotionally prepare yourself, after hearing “circular list” and “mutation”. There will also be OO objects.
…
class spinlock steps =
object (self)
val mutable buffer = (let rec list = 0 :: list in list)
val mutable zero = []
val mutable n = 1
val steps = steps
method buffer = buffer
method zero = zero
method show =
Printf.printf "(%d)" (List.hd buffer);
for i = 1 to 3 do
Printf.printf " %d" (List.nth buffer i);
done; print_newline ()
method step = for i = 1 to steps do buffer <- List.tl buffer done
method insert =
self#step;
Obj.set_field (Obj.repr buffer) 1 (Obj.repr (n :: (List.tl buffer)));
buffer <- List.tl buffer;
n <- n + 1
initializer zero <- buffer
end
let part0 =
let p = new spinlock 3 in
p#show;
p#insert;
p#show;
p#insert;
p#show;
p#insert;
p#show;
p
let part1 =
let p = new spinlock 301 in
for i = 1 to 2017 do
p#insert
done;
p#show;
p
part1
gives different output depending on whether part0
happens (as it does above) or not (as when it is commented out).
I guessed that buffer
was getting shared between the spinlocks and was able to avoid the bug, getting correct answers, by passing an initial value:
class spinlock steps init =
object (self)
val mutable buffer = (let rec list = init :: list in list)
...
But this seems like something that only accidentally works. Is there a more reliable way to ensure that each spinlock
is mutating its own private list?