There are four kinds of dynamic predicates: Global flags, global variables, per-object flags, and per-object variables. Global variables have to be declared:
(global variable (current player $))
The other three kinds are distinguished by the number of parameters:
(I am the walrus) %% no parameter -> global flag
(#rose is red) %% one parameter -> per-object flag
(#hovercraft is full of #eels) %% two parameters -> per-object variable
In the case of per-object variables, the first parameter always indicates the object, and the second parameter is the value of the variable. That’s just how per-object variables work, by definition, and I agree with you that it’s different from other kinds of predicates. But it’s very useful.
($X is $Y $Z) is an access predicate (a kind of alias, or hygienic macro) for two per-object variables:
($X has relation $Y) and
($X has parent $Z).
So when you do
(now) (#apple is #heldby #player), you’re actually setting two separate per-object variables:
(now) (#apple has relation #heldby)
(now) (#apple has parent #player)
In both cases, the first parameter identifies what object to modify, and the second parameter is the value to store in the per-object variable.