简体   繁体   中英

Expressing infinite kinds

When expressing infinite types in Haskell:

f x = x x -- This doesn't type check

One can use a newtype to do it:

newtype Inf = Inf { runInf :: Inf -> * }

f x = x (Inf x)

Is there a newtype equivalent for kinds that allows one to express infinite kinds?

I already found that I can use type families to get something similar:

{-# LANGUAGE TypeFamilies #-}
data Inf (x :: * -> *) = Inf
type family RunInf x :: * -> *
type instance RunInf (Inf x) = x

But I'm not satisfied with this solution - unlike the types equivalent, Inf doesn't create a new kind ( Inf x has the kind * ), so there's less kind safety.

Is there a more elegant solution to this problem?

Responding to:

Like recursion-schemes, I want a way to construct ASTs, except I want my ASTs to be able to refer to each other - that is a term can contain a type (for a lambda parameter), a type can contain a row-type in it and vice-versa. I'd like the ASTs to be defined with an external fix-point (so one can have "pure" expressions or ones annotated with types after type inference), but I also want these fix-points to be able to contain other types of fix-points (just like terms can contain terms of different types). I don't see how Fix helps me there

I have a method, which maybe is near what you are asking for, that I have been experimenting with . It seems to be quite powerful, though the abstractions around this construction need some development. The key is that there is a kind Label which indicates from where the recursion will continue.

{-# LANGUAGE DataKinds #-}

import Data.Kind (Type)

data Label = Label ((Label -> Type) -> Type)
type L = 'Label

L is just a shortcut to construct labels.

Open-fixed-point definitions are of kind (Label -> Type) -> Type , that is, they take a "label interpretation (type) function" and give back a type. I called these "shape functors", and abstractly refer to them with the letter h . The simplest shape functor is one that does not recurse:

newtype LiteralF a f = Literal a  -- does not depend on the interpretation f
type Literal a = L (LiteralF a)

Now we can make a little expression grammar as an example:

data Expr f
    = Lit (f (Literal Integer))
    | Let (f (L Defn)) (f (L Expr))
    | Var (f (L String))
    | Add (f (L Expr)) (f (L Expr))

data Defn f
    = Defn (f (Literal String)) (f (L Expr))

Notice how we pass labels to f , which is in turn responsible for closing off the recursion. If we just want a normal expression tree, we can use Tree :

data Tree :: Label -> Type where
    Tree :: h Tree -> Tree (L h)

Then a Tree (L Expr) is isomorphic to the normal expression tree you would expect. But this also allows us to, eg, annotate the tree with a label-dependent annotation at each level of the tree:

data Annotated :: (Label -> Type) -> Label -> Type where
    Annotated :: ann (L h) -> h (Annotated ann) -> Annotated ann (L h)

In the repo ann is indexed by a shape functor rather than a label, but this seems cleaner to me now. There are a lot of little decisions like this to be made, and I have yet to find the most convenient pattern. The best abstractions to use around shape functors still needs exploration and development.

There are many other possibilities, many of which I have not explored. If you end up using it I would love to hear about your use case.

With data-kinds, we can use a regular newtype:

import Data.Kind (Type)

newtype Inf = Inf (Inf -> Type)

And promote it (with ' ) to create new kinds to represent loops:

{-# LANGUAGE DataKinds #-}

type F x = x ('Inf x)

For a type to unpack its 'Inf argument we need a type-family:

{-# LANGUAGE TypeFamilies #-}

type family RunInf (x :: Inf) :: Inf -> Type
type instance RunInf ('Inf x) = x

Now we can express infinite kinds with a new kind for them, so no kind-safety is lost.

  • Thanks to @luqui for pointing out the DataKinds part in his answer!

I think you're looking for Fix which is defined as

data Fix f = Fix (f (Fix f))

Fix gives you the fixpoint of the type f . I'm not sure what you're trying to do but such infinite types are generally used when you use recursion scehemes (patterns of recursion that you can use) see recursion-schemes package by Edward Kmett or with free monads which among other things allow you to construct ASTs in a monadic style.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM