Algol 60
Here's a boolean procedure
that does what the question asks for (note: Algol 60 is defined in terms of a list of tokens without fixing syntax for them, the below uses Marst syntax to represent the individual tokens that make up the program):
boolean procedure recursion detector(n);
boolean n;
begin
own boolean nested, seen nested;
boolean was nested, retval;
was nested := nested;
begin if nested then seen nested := true end;
nested := true;
retval := n; comment "for the side effects, we ignore the result";
nested := was nested;
retval := seen nested;
begin if ! nested then seen nested := false end;
recursion detector := retval
end;
Verification
Here's the test code I used:
procedure outboolean(c, b);
integer c;
boolean b;
begin
if b then outstring(c, "true\n") else outstring(c, "false\n")
end;
begin
outboolean(1, recursion detector(false));
outboolean(1, recursion detector(true));
outboolean(1, recursion detector(recursion detector(false)));
outboolean(1, recursion detector(false | recursion detector(true)));
outboolean(1, recursion detector(false & recursion detector(true)));
outboolean(1, recursion detector(recursion detector(recursion detector(false))))
end
As expected, the output is:
false
false
true
true
true comment "because & does not short-circuit in Algol 60";
true
Explanation
Algol 60 has a different evaluation order from most languages, which has a logic of its own, and is actually much more powerful and general than the typical evaluation order, but is fairly hard for humans to get their head around (and also fairly hard for computers to implement efficiently, which is why it got changed for Algol 68). This allows for a solution with no sort of cheating (the program doesn't need to look at the parse tree or anything like that, and unlike almost all the other solutions here, this would work just fine if the nested call were made via an FFI).
I also decided to show off a few other quirks of the language. (Notably, variable names can contain whitespace; this is fairly useful for readability, because they can't contain underscores. I also love the fact that the comment indicator is the literal word comment
in most syntax encodings. Algol 68 found this fairly awkward for short comments, and introduced ¢
as an alternative. The quotes around the comment body aren't normally needed, I just add them for clarity and to prevent the comment accidentally ending when I type a semicolon.) I actually really like the broad concepts of the language (if not the details), but it's so verbose that I rarely get to use it on PPCG.
The main way in which Algol 60 differs from the languages it inspired (such as Algol 68, and indirectly C, Java, etc; people who know K&R C will probably recognise this syntax for functions) is that a function argument is treated a bit like a little lambda of its own; for example, if you give the argument 5
to a function that's just the number 5, but if you give it the argument x+1
you get exactly what you specified, the concept of "x
plus 1", rather than the result of x
plus 1. The difference here is that if x
changes, then attempts to evaluate the function argument in question will see the new value of x
. If a function argument isn't evaluated inside the function, it won't be evaluated outside the function either; likewise, if it's evaluated multiple times inside the function, it'll be evaluated separately each time (assuming that this can't be optimised out). This means that it's possible to do things like capture the functionality of, say, if
or while
in a function.
In this program, we're exploiting the fact that if a call to a function appears in an argument to that function, that means that the function will run recursively (because the argument is evaluated at exactly the point or points that the function evaluates it, no earlier or later, and this must necessarily be inside the function body). This reduces the problem to detecting if the function is running recursively, which is much easier; all you need is a thread-local variable that senses if there's a recursive call (plus, in this case, another one to communicate information back the other way). We can use a static variable (i.e. own
) for the purpose, because Algol 60 is single-threaded. All we have to do after that is to put everything back the way it was, so that the function will function correctly if called multiple times (as required by PPCG rules).
The function doesn't return the desired value from the inside calls at the moment (at least if you assume they should look for self-calls in only their arguments, rather than counting themselves); making that work is fairly easy using the same general principles, but more complex and would obscure the workings of the function. If that's deemed necessary to comply with the question, it shouldn't be too hard to change.
print(func(), func(func()))
, or will there only ever be a top-level call to the function right after it has been defined? \$\endgroup\$