Are arbitrary rank types and existentials equivalent?

Maybe one subsumes the other? What I want to know is if there is an easy way to emulate one with the other, and how much convenience is lost in doing so. For instance, is it possible to implement stream fusion with rank2 types, or the ST monad with existantials? Is there any paper discussing this kind of things? Thanks, Loup

On Sun, 2008-11-09 at 13:46 +0100, Loup Vaillant wrote:
Maybe one subsumes the other?
What I want to know is if there is an easy way to emulate one with the other, and how much convenience is lost in doing so.
For instance, is it possible to implement stream fusion with rank2 types, or the ST monad with existantials?
Is there any paper discussing this kind of things?
exists a. F a ~ forall r. (forall a. F a -> r) -> r runST :: exist s. ST s a -> a -- this type is implied by runST's but does not imply runST's so it is less general There are various rules for moving quantifiers around. Any text on intuitionistic predicate logic should list the rules. However, this is replacing existentials with higher rank uses of universals. Higher rank types applies to any quantifier, and so to attempt to replace higher rank uses of universals would require higher rank uses of existentials in general.

There's a natural relation between higher rank types and existentials; one way to think about it is this: if you have some existential type t (subject to some constraints), you cannot operate on it except with some function that accepts any type t subject to those constraints. There is a simple encoding of existential types using higher rank types: Given
data E a1..an = forall e1...en. (constraints) => E t1..tn where t1..tn are types in terms of a1..an, we replace with data E' a1..an = E' (forall b. (forall e1..en. t1 -> t2 -> ... -> tn -> b) -> b)
Any function f that pattern matches on E can use E' instead in the following way:
f (E v1..vn) = exp becomes f (E' exist) = exist f' where f' v1..vn = exp
An example:
data HasShow = forall a. Show a => HasShow a data HasShow' = HasShow' (forall b. (forall a. Show a => (a -> b)) -> b)
useShow :: HasShow -> String useShow (HasShow x) = show x
useShow' :: HasShow' -> String useShow' (HasShow' f) = f (\x -> show x)
I don't think there is much a link between fusion and the type system; stream fusion is a compilation technology, like inlining. To demonstrate, here's a simple fusion engine using this encoding:
{-# LANGUAGE RankNTypes #-} module Fusion where
data StreamR s a = Done | Skip s | Yield a s newtype Stream a = Stream (forall b. (forall s. s -> (s -> StreamR s a) -> b) -> b)
stream :: [a] -> Stream a stream xs = Stream (\f -> f xs go) where go [] = Done go (x:xs) = Yield x xs {-# NOINLINE[1] stream #-}
unstream :: Stream a -> [a] unstream (Stream k) = k loop where loop s next = case (next s) of Done -> [] Skip s' -> loop s' next Yield a s' -> a : loop s' next {-# NOINLINE[1] unstream #-}
You then end up with the same (stream (unstream s) = s) rule:
{-# RULES "stream/unstream" forall s. stream (unstream s) = s #-}
Now lets implement a stream version of map:
mapS f = unstream . mapStream f . stream mapStream :: (a -> b) -> Stream a -> Stream b mapStream f (Stream k) = k mapStream' where mapStream' s0 next = Stream (\g -> g s0 go) where go s = case next s of Done -> Done Skip s' -> Skip s' Yield a s' -> Yield (f a) s'
test :: [Int] -> [Int] test = mapS (+1) . mapS (*2)
Looking at the core generated for "test", we see that the loops are indeed fused: (case x_ag7 of wild1_anS { GHC.Types.I# x1_anU -> GHC.Types.I# (GHC.Prim.+# (GHC.Prim.*# x1_anU 2) 1) }) (which basically says "x_ag7 * 2 + 1") I don't think there is an encoding that goes from higher rank types to existentials, however. Existentials lose information which cannot be recovered. I can't think of a way to use existentials to encode, for example, natural transformations on functors, while maintaining that the type "inside" the functor remains the same. Here's some code to make this idea concrete:
type Trans f g = (forall a. f a -> g a) compose :: Trans g h -> Trans f g -> Trans f h compose g2h f2g = \fa -> g2h (f2g fa)
You can represent a "lossy" transformation between functors as:
data Any f = forall a. Any (f a) type LossyTrans f g = Any f -> Any g
But then there is no way to know that the "a" held in the "Any f" matches the "a" in the "Any g".

Thank you, everyone. You have addressed my concerns very accurately.
So in short, higer rank types subsume existentials. Good. And the burden
of emulating existentials can be lowered by a suitable macro system.
Very good.
2008/11/9 Derek Elkins
There are various rules for moving quantifiers around. Any text on intuitionistic predicate logic should list the rules.
Err, where can I find such texts? I don't even understand "intuitionistic predicate logic" :-( Cheers, Loup

On 11/09/08 17:04, Loup Vaillant wrote: [snip]
Err, where can I find such texts? I don't even understand "intuitionistic predicate logic" :-( I just googled that phrase and got many hits. I think metaprl implements something like that:
http://metaprl.org/default.html I've often wondered about haskell and metaprl. I know metprl has something like a hierarchy of types and that's used to avoid Russell's paradox. I also remember reading somewhere (can't remember where) that there was something in category theory that had a hierarchy of categories. Maybe someone else can provide more details.

Ryan Ingram schrieb:
There's a natural relation between higher rank types and existentials; one way to think about it is this: if you have some existential type t (subject to some constraints), you cannot operate on it except with some function that accepts any type t subject to those constraints.
There is a simple encoding of existential types using higher rank types:
Given
data E a1..an = forall e1...en. (constraints) => E t1..tn where t1..tn are types in terms of a1..an, we replace with data E' a1..an = E' (forall b. (forall e1..en. t1 -> t2 -> ... -> tn -> b) -> b)
Can you please put this on the Wiki?
participants (5)
-
Derek Elkins
-
Henning Thielemann
-
Larry Evans
-
Loup Vaillant
-
Ryan Ingram