Xifed points

While discussing the self-application of call/cc, I noted that ((call/cc call/cc) x) is the self-application (x x). Since (call/cc call/cc) is itself a self-application, there’s a fascinating fact: (call/cc call/cc) is equivalent to ((call/cc call/cc) call/cc), and we can keep applying the result to call/cc ad infinitum.

Now, this brings to mind the fixed-point combinator. The fixed-point combinator is defined so:

(fix f) = (f (fix f))

What about the reverse?

(xif f) = ((xif f) f)

Then (xif call/cc) could be (call/cc call/cc), since that satisfies the above equation. However, xif as defined above does not converge.


Now, in a call-by-value calculus, neither does fix. Call-by-value demands that the argument is available before evaluating an application. Call by name dispenses with that requirement, but it still requires the function to be available before evaluating an application. Can we eliminate this assumption?

In any case, can we find a meaning for this xif operator? Let's step into the monad world for a moment to clarify things.

Interpretations of fixed points

If f is a monadic function (of type a -> m a), we can write a fixed-point operator for it like so:

fixM f = (fixM f) >>= f

This does not converge, but it does capture the meaning of fix: it is the value that, when f is applied to it, does not change.

If we take the dual of fixM, we get xifW, which is analogous to a xifed-point operator for comonad functions:

xifW f = extend f (xifW f)

Interpreting the comonads

We can try to interpret this function using some analogies. Comonads are like objects, and comonad functions are like commands. So the xifed-point of a command is the object you get by passing that command over and over again to an object.

Which object? You can have a different xifed-point if you start with different objects, just as you can have different fixed points if you start with different base cases. Normally the definition of fix and xif ignore the starting point (and so only make sense when there is one fixed/xifed-point). This is why using fix to define the factorial does not define what happens to negative inputs.

We can write fix and xif in an imperative style to make the dependence on the initial object clear:

def fix(initial, f):
    while True:
        next = f(initial)
        if next == initial: break
        initial = next
    return initial

def xif(initial, f):
    while True:
        next = initial.execute(f)
        if next == initial: break
        initial = next
    return initial

We only use fix and xif in the usual forms because we are working with fixed/xifed-points that do not depend on the initial conditions in any interesting way.

When are xifed-points meaningful?

For xifed-points to be meaningful there have to be arguments for which the result of a function call does not depend on the function. So let's imagine that there exists a command become0, such that (f become0) is 0 for all f. You can think of this as a hard-reset command.

Then xif become0 must be 0, since 0 become0 = 0. Similarly, we can build an entire language of commands with if and such, and define their behavior on arbitrary objects. All we'd have done is shown that we can write all applications backwards and then compute fixed-points. So aren't fixed-points and xifed-points more or less identical, with the duality pretty meaningless?

But it is kind-of weird that the fixed-point operator can be typed, and the xifed-point operator cannot.

By on . Share it—it's CC-BY-SA licensed.

Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author and do not necessarily reflect the views of the National Science Foundation.