_    __    ___  
   / \  / /_  ( _ ) 
  / _ \| '_ \ / _ \           Algol 68: identity relations and nil
 / ___ \ (_) | (_) |
/_/   \_\___/ \___/ 
                    



                                                     Jose E. Marchesi
                                                      January 1, 2026


Identity relations are fun when mixed with nil!

Names and references

In Algol 68 references are not explicitly dereferenced, like in other languages. Instead, dereferencing always happens automatically, as required, depending on the syntactic context where the name appears.

Identity relations

An identity relation:
A :=: B
compares two names. Both 'a' and 'b' are supposed to be of the same mode, which has to be a name, i.e. a reference to something. This is an error:
int i = 10, j = 20;

i :=: j
Because 'i' and 'j' are integral values, not names. This works:
int i, j;

i :=: j
But the identity relation always yields 'false'. Both 'i' and 'j' are of mode ref int, and the values ascribed to them (not the values they refer to) are names generated by the implicit sample generators in the variable declaration. The names generated by a generator will always be disjoint, like in most if not all languages variables are occupy disjoint storage, so the identity relation will always yield false. In the same scenario, 'i :=: i' would always yield 'true'. Now consider:
int i;
ref int j := i;

i :=: j
In that identity relation we have a ref int at the lhs ('i') and a ref ref int at the rhs ('j'). Since an identity relation expects names of the same mode in both sides, if it is to be valid then one of the sides should be suitable to be coerced to the mode in the other side. To determine what side gets coerced, and how, balancing occurs. Basically, one of the sides is chosen to be in a soft context, the other in a strong context. Then the tertiary at the strong side gets coerced to the mode of the tertiary at the soft side In the example above, it is clear that 'j', which is of mode 'ref ref int', can be coerced to 'ref int', which is the mode of i, by dereferencing, so the lhs is chosen to be the soft context and the rhs is chosen to be the strong context. Therefore, in this particular example, the identity relation yields the answer to: is 'j' referring to 'i'? Note how in Algol 68 there is no explicit indirecting operator/construct, so one has to rely on coercions in order to dereference.

nil

Now, nil is a name that can refer to values of any mode. This doesn't mean nil has a special mode like 'ref anything'. What this means is that particular instances of nil in the program have particular modes, depending on the context where they appear. For example the mode of the nils in:
ref int foo = nil;
int bar := nil;
is 'ref int'. But the mode of the nils in:
ref ref int baz = nil;
ref int quux := nil;
is 'ref ref int'. In other words, nil can only appear in a strong context, which are syntactic contexts where it is possible to determine the expected mode statically. This means that whenever nil appears in an identity relation, it always occupies the strong position. Therefore it is the other side that determines the mode, no coercions are ever done... and here is where problems usually start. Consider the typical list traversing code:
ref Node node := head;

while node :/=: nil
do process_node (node);
   ptr := next of node
od
The mode of node is 'ref ref Node'. Since nil always occupies the strong position, we are comparing a 'ref ref Node' with a 'ref ref Node' nil... which always yields false, because the identity relation above answers the question: is node itself nil? It will never be nil, the identity relation will always yield true, our program is wrong, and will result in a run-time error or a segmentation fault if we compile without -fcheck=nil. What we want to know is whether the value referred by 'node' is nil. We can use a cast:
while ref Node (node) :/=: nil
do process_node (node);
   ptr := next of node
od
The cast puts node in a strong context with goal mode 'ref Node', and that results in dereferencing it once. The resulting value is a name of mode ref Node, which is used in the identity relation and compared to a 'ref Node' nil. Now this does answer the question we want: is the value referred by 'node' nil? The code is correct. So as it happens, when nil is involved in practice it is always necessary to use casts in identity relations in order to "fix" the mode on the soft side and determine how much dereferencing happens on the strong side. This can be annoying and error prone, and it was early recognized that the apparently symmetric identity relation is really not that symmetric as its syntax suggests. In fact, in an ALGOL bulletin (AB40.4.3 R. Haentjens, Proposal for a Simple Syntax for the ALGOL 68 unit) an alternative syntax was proposed for identity relations, due to this reason. Fortunately there is a clever trick, used by McGettrick, Lindsey and others, that makes this problem go away and in fact makes programs more readable: for every mode you define, define also a "nihil" for it if you are going to operate with references to it. Then use the nihil in identity relations and neve use nil directly. For example:
mode Node = struct (int payload; ref Node next);
ref Node no_node = nil;

while node :/=: no_node
do process_node (node);
   ptr := next of node
od
The nil stored in no_node has mode 'ref Node', so when it appears in the identity declaration it becomes the soft side and makes 'node' (in the strong side) to be coerced to 'ref Node'. In this case, node gets dereferenced once, which is what we want. Hope this helps.