You're reading the public-facing archive of the Category Theory Zulip server.
To join the server you need an invite. Anybody can get an invite by contacting Matteo Capucci at name dot surname at gmail dot com.
For all things related to this archive refer to the same person.
Has anyone internalized (no pun intended) this paper: https://arxiv.org/abs/1605.02206? Basically it's a type theory that defines function types as a particular inductive, and has normalization as a by-product. I've been thinking this is possible for a while now, so I'm excited to understand what makes it tick, and whether this general approach can be applied to other type theories.
I haven't seen that paper, but I'm familiar with another one that can define functions. It's more like System F_ω, though.
I guess the setting I'm familiar with is rather different, though. It doesn't attempt to have strong normalization, I think, and defining functions as 'data' would be kind of wrong. They are codata (that gives the correct evaluation strategy).
Co-inductive, sure. What's the other paper?
Compiling with Classical Connectives.
It is also different from this paper in that it has multiple things on the right of the turnstile, and (co)constructors can have arbitrarily many things to the left and right of the turnstile.
Ugh classical logic. Thanks for the reference though! I really do enjoy this "function spaces are nothing special" philosophy.
It is not just classical logic.
It is more like 'classical' linear logic, but it isn't linear, it has polarity shifts instead.
And the polarity shifts correspond to evaluation strategies that make the reduction behavior unambiguous.
So, e.g. A + B
is not the same as A ⅋ B
, because the evaluation strategy is different, and A
and B
cannot even be the same in either case, because +
requires A
and B
to be positive, while for ⅋
they must be negative.
And ⅋
is defined in a similar way to functions if you use the calculus for arbitrary (co)data definitions. It is the codata type with a destructor like A ⅋ B ⊢ A,B
I think. Functions are like A , A → B ⊢ B
, but you could use a ¬
codata type to mediate between the difference.
I guess I should say (since it's probably not obvious), the similarity between the two is that ⅋
is a codata type with one destructor with two parameters, both negative. →
is a codata type with one destructor with two parameters, one positive and one negative.
I think the original question is still a good one: has anyone formalized either of those approaches?
I saw a few formalizations in Coq of that are related to this point of view (the development of this paper for instance -- gives you let-binding as well as pattern-matching, whereas provides some way to sequence contexts, as well as copattern-matching).
I have yet to read in details the paper above, but I think one annoying point is the treatment of universes. Sure you can provide Inductive-recursive models of universes but I don't think there is any such "symmetric" presentation of type theory incorporating the universes well (I would love to be proven wrong though).
Yeah, I'm not really sure how to handle dependent types, because they're kind of inherently biased toward terms.
Whereas the stuff wants to put terms, coterms and cuts on more equal footing.
(Assuming it's like the paper I mentioned above, which does have those operations.)
Dan Doel said:
I guess I should say (since it's probably not obvious), the similarity between the two is that
⅋
is a codata type with one destructor with two parameters, both negative.→
is a codata type with one destructor with two parameters, one positive and one negative.
That's cool, but I have very bad intuition about this. Is there a "standard" PL construct of type ⅋
?
I don't know. It probably won't look very nice, because the way a lot of stuff is presented means that use of ⅋
is necessarily obscured. It is a type that is natural when you are about to manipulate continuations as easily as values, but most things are designed so that continuations are implicit, to limit what you can do with them.
A value p : A ⅋ B
is something you can supply with two continuations, so in some ways you can think of it as something where you can do match p with { L a -> ... ; R b -> ... }
. In a linear setting, I think the expectation you should have is that both branches run (instead of A + B
, where only one runs). In a non-linear setting, this is relaxed, but still multiple branches may run, and some branches may run multiple times, I think (and this is contrary to A + B
where again one branch runs exactly once).
So, A ⅋ B
is kind of like the "devil's bargain" interpretation of excluded middle. But the important part is that the calculus is structured so that this computational behavior is not conflated with the type A + B
. The latter is intrinsically call-by-value, so its values are actually reduced values that actually look like L x
or R y
, and it makes no sense for the "devil's bargain" to be values of that type. By contrast, A ⅋ B
is inherently call-by-name, so its values can have non-trivial computational behavior.
Some of this can be seen in A → B = ¬ A ⅋ B
. To call the function I think we must do match f with { L k → jump k x ; R b → b }
. The first branch is, I think, run as many times as the argument is referred to directly in the body of the function. This may actually be 0 times, even, but I think it may be multiple times as well, although it isn't doing any significant computation here (because ¬
just turns a call-by-value type into call-by-name). The second branch is evaluated with the result of the function. But actually B
is call-by-name because ⅋
requires its sub-expressions to be so, so how many times we go through the second branch might depend on how many times we use the name of this overall expression. So in some sense, even "standard" call-by-name scenarios are doing something a lot like the bargain, jumping back to the same place over and over. It just isn't presented that way.
In the calculus from the paper I mentioned, function values look like λ[x,α]. c
, where x
is the argument, and α
is the return continuation, and c
says how they should be used together. So, like, the identity function is λ[x,α]. <x|α>
. Function continuations are like [x,α]
, which is like 'pushing x
onto the stack α
.' And values of A ⅋ B
are like λ[α,β]. c
, while continuations are like [α,β]
. Values of A + B
are like L x
and L y
, and continuations are like case { L x. c1 ; R y. c2 }
. So there is more obvious distinction in the syntax than I used above, but I'm not sure how to maintain that in world where you are required to only talk about terms.
Cool, thanks for the clarification Dan! This makes some sense.
Is it fair to say that a function in this kind of calculus can be made to go in two ways: either providing some input or requesting some output? I think this is how I'd make sense of (both of them being equivalent to ).
I'm not sure what you mean exactly. I guess I wouldn't say that for linear logic, because the idea is those aren't 'two modes' at all, they're just two equivalent ways of looking at the same thing, which is that there is an 'input pipe' for an and an 'output pipe' for a , and things may happen in any order/simultaneously up to data dependencies. Unless you mean that 'an input pipe for an input pipe is an output pipe'. But it might be better to to think of putting an adapter on the output of a function than the function having multiple modes for that.
In Compiling with Classical Connectives it is more complicated, because negation mediates between evaluation strategies, and so there are multiple negations. If is call-by-value, then is call-by-name, but if is call-by-name, then is not even well formed. Instead you have which is call-by-value.
This has connections with different ways of viewing continuations. If is call-by-value, then its values are like trees built out of 'constructors'. Then values of are, I think, suspended computations waiting on an value to proceed. Continuations/covalues of are just wrapped values of .
If is call-by-name, then its values are already suspended computations, and its continuations are like trees of 'observations' or instructions to perform on a corresponding computation. So the roles of value and continuation are kind of swapped from above. Then a value of is like a captured tree/stack representation of a continuation. And a continuation of is a suspended computation waiting to be matched up with one of these captured stacks.
So, then, if you think about all this, you should hopefully be able to work out that and , and that this has something to do with inserting/removing some wrappers that kind of make my description of the 'trees' and such overly naive. :slight_smile: And I think functions still don't run in 'two modes' per se, it's probably more like snapping adapters on either side.
(There are even more complications that I haven't mentioned yet, but I think they're not actually relevant for this example.)
This kind of shows you the bias toward values of a lot of type theory. The call-by-value stuff is inductive, and is thought of as concrete(-ish), well-founded trees. But that is describing its values. Coinductive stuff has concrete trees, too, but as the continuations, which you can't really access in type theory. But you can define an inductive type that essentially the same as . So you might not be really losing out on anything in absolute terms, but it is somewhat obscured.
Hmm, actually, maybe my description wasn't overly naive, too. I guess the point of and is that they dualize things in a way that matches up the 'simple' parts. So, a continuation is concrete presuming that values are concrete, and a value is concrete presuming that a continuation is concrete. And continuations are concrete because values are concrete, and continuations are concrete (push a concrete value on a concrete stack). It is probably the 'additional complications' that make the description naive.
@Cody Roux I think one way to think about this, based on my rambling above, is that the calculus actually explicitly talks about (suspended) computations and concrete trees at every type. When you do this, functions are definable because it is just one type where the values are suspended computations, and the continuations are concrete trees.
But in a lot of type theory, they only want to talk about values, and moreover, they almost exclusively want to talk about types where the values are the concrete trees (bias toward induction). Then functions are the one exception, and all of the 'suspended computation' stuff gets dumped into its values. Then all of the difficulty of normalization, extensionality, etc. gets presented as being about functions, because that is the one 'escape hatch' type used to embed all the computational behavior. When other M-types are included, they also inherit some difficulties, because of the value bias (their values are suspended computations, which want extensionality, rather than trees, which are content with intensionality).
In CwCC, computations are their own thing to talk about. is a term of type , is the evaluation strategy of type , is a co-term of type , and and classify the free variables used in and . Then normalization is clearly about that, not about function values.