See also my answer to how the yin yang puzzle works, which I had to figure out an answer to before I could answer this one.
Being a "typed" language does not, by itself, make the difference to whether this puzzle is expressible in it (no matter how vague the term "typed language" is). However, to answer your question most literally: yes, it’s possible, because Scheme itself is a typed language: each value has a known type. This is obviously not what you meant, so I assume you mean whether this is possible in a language where each variable is assigned a permanent type that never changes (a.k.a. "statically typed language").
Moreover, I’ll assume that you want the spirit of the puzzle preserved when expressed in some language. Obviously it’s possible to write a Scheme interpreter in x86 machine code, and obviously it’s possible to write an x86 machine code interpreter in a typed language which only has integer data types and function pointers. But the result isn’t in the same "spirit". So to make this more precise, I will place an extra requirement: the result must be expressed using true continuations. Not an emulation, but real full-on continuations.
So, can you have a statically typed language with continuations? It turns out you can, but you might still call it cheating. For example, in C#, if my continuations were defined as "function that takes an object and returns an object", where "object" is a type that can hold anything at all, will you find this acceptable? What if the function takes and returns a "dynamic"? What if I have a "typed" language where every function has the same static type: "function", without defining the types of arguments and return types? Is the resulting program still in the same spirit, even though it uses true continuations?
My point is that the "statically typed" property still allows for a huge amount of variation in the type system, enough to make all the difference. So just for fun, let’s consider what the type system would need to support in order to qualify as non-cheating by any measure.
The operator call/cc(x)
can also be written as x(get/cc)
, which is much easier to comprehend in my opinion. Here, x
is a function that takes a Continuation and returns a value, while get/cc
returns a Continuation
. Continuation
has all traits of a function; it can be called with one argument, and will sort of substitute the value passed in to wherever get/cc that created it was originally located, additionally resuming execution at that point.
This means that get/cc has an awkward type: it’s a function
, but the very same location will eventually return a value whose type we don’t know yet. Suppose however, that in the spirit of statically-typed languages, we require the return type to be fixed. That is, when you call the continuation object, you can only pass in values of a predefined type. With this approach, the type of the continuation function can be defined with the recursive expression of the form T = function T->T
. As pointed out by a friend, this can type actually be declared in C#: public delegate T T(T t);
!
So there you have it; being "typed" does not preclude nor guarantee that you can express this puzzle without altering its nature. However, if you allow for the static type "can be anything" (known as object
in Java and C#), then the only other thing you need is support for true continuations, and the puzzle can be represented no problem.
Approaching the same question from a different perspective, consider my rewrite of the puzzle into something more reminiscent of a traditional statically-typed imperative language, which I explained in the linked answer:
yin = (function(arg) { print @; return arg; })(get-cc);
yang = (function(arg) { print *; return arg; })(get-cc);
yin(yang);
Here, the type of yin
and yang
never changes. They always store a "continuation C that takes a C and returns a C". This is very much compatible with static typing, whose only requirement is that the type doesn’t change next time you execute that code.