Bug in 0m/03: Unable to append to a list twice

This might be related to Bugs with non-linear unification

When append is called twice on a list, the second one fails. Here’s a minimal example:

(global variable (tasks []))
(program entry point)
	(insert 1)
	(tasks $cur)
	tasks has: $cur
	(insert 2)
	(tasks $cur)
	tasks has: $cur

(insert $item)
	(tasks $list)
	(append $list $item $finalList)
	(now)(tasks $finalList)

The final result should be: [1 2], however the built-in append function fails.

3 Likes

A slightly different way of implementing this via head/tail also fails:

(global variable (tasks []))
(program entry point)
	(insert 1)
	(tasks $cur)
	tasks has: $cur
	(insert 2)
	(tasks $cur)
	tasks has: $cur

(insert $item)
	(tasks $list)
	($finalList = [$item | $list])
	(now)(tasks $finalList)
2 Likes

This explains why when I start a new project I run into a wall early on: All my ideas require one to be able to append to a list more than once.

While we wait for an official fix, has anyone come up with a work around?

Trying to troubleshoot this, I would put in two intermediate output prints:

%% rest as above, but (insert $item) changed like this:

(insert $item)
	(tasks $list)
	inserting $item into $list (line)
	(append $list $item $finalList)
	appended $item to $list, resulting in $finalList (line)
	(now)(tasks $finalList)

… which results in:

inserting 1 into []
appended 1 to [], resulting in 1
tasks has: 1
inserting 2 into 1

… and then the output stops, which indicates that the next append step isn’t working. Our added output shows why:

Appending 1 to the empty list results in the number 1, not in [1] (a list with 1 as the sole element). And in the next step, appending 2 to 1 won’t work, since 1 isn’t a list.

One way to do it is to append [$item] (a list with $item as the element) instead of $item to the list. The append function will then concatenate the lists, resulting in the correct output list.

So, the line (append $list $item $finalList) should rather be: (append $list [$item] $finalList).

(One caveat: this way, if you pass something into “insert” which is not a single item, but a list already, then you will end up with a nested list like [1 2 [3 4]]. So depending on the use case in your game, you might find it necessary to check with “(if) (list …)” whether you are dealing with a list already. In that case, you can directly concatenate them with (append $list $item $finalList).)

3 Likes

This fails not at the inserting stage, but because you’re calling (tasks $cur) repeatedly.

As the docs say: Chapter 6: Dynamic predicates

As usual, we can query the predicate with a bound parameter (to check if the global variable has that particular value), or with an unbound variable (to obtain the current value).

The first time, $cur was previously unbound and is then bound to the then-current value of the global variable (which is [1] at that time).

The second time, $cur is already bound (from the first time), and so (tasks $cur) will check if the current value of the global variable ([2 1]) has the value of $cur ([1]), which is not the case. And since the query fails, the execution stops at that point.

So, one way to repeatedly check on the value is to introduce new bound variables, $cur2 etc.:

(global variable (tasks []))
(program entry point)
	(insert 1)
	(tasks $cur)
	tasks has: $cur
	(insert 2)
	(tasks $cur2)
	tasks has: $cur2

(This also applies to the (tasks $cur) queries in the first example above, with append.)

2 Likes