Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 546
yairchu Avatar asked Dec 16 '18 19:12

yairchu


People also ask

How do you express infinite?

The infinity symbol ∞ is sometimes called the lemniscate and is a mathematical symbol representing the concept of infinity. The sign of infinity is used more often to represent a potential infinity, rather than to represent an actually infinite quantity such as the ordinal numbers and cardinal numbers.

What are the different types of infinite?

Three main types of infinity may be distinguished: the mathematical, the physical, and the metaphysical.

What is an example of something infinite?

Another good example of infinity is the number π or pi. Mathematicians use a symbol for pi because it's impossible to write the number down. Pi consists of an infinite number of digits. It's often rounded to 3.14 or even 3.14159, yet no matter how many digits you write, it's impossible to get to the end.

What are the different types of infinitives?

Types of Infinitive: An infinitive can be a to-infinitive or bare infinitive (without to). There is no difference between them. So we can classify them as follow. 1. Bare infinitive 2. Full infinitive 3. Split infinitive. 1. Bare infinitive:

What does the word infinite mean in math?

The term “infinite” represents limitless or unboundedness. It is denoted by the letter” ∞ “. To solve systems of an equation in two or three variables, first, we need to determine whether the equation is dependent, independent, consistent, or inconsistent.

What is an example of an infinite solution?

As an example, consider the following two lines. These two lines are exactly the same line. If you multiply line 1 by 5, you get the line 2. Otherwise, if you divide the line 2 by 5, you get line 1. Show that the following system of equation has infinite solution: 2x + 5y = 10 and 10x + 25y = 50

How do you use full infinitives?

When an infinitive is used with the word ‘to’ or to + infinitive is called full infinitive. To + infinitive = full infinitive. Full infinitives can function as a noun, an adjective or an adverb in a sentence. To call, after all difficulties doesn’t seem worthwhile.


3 Answers

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, e.g., 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.

like image 71
luqui Avatar answered Oct 09 '22 14:10

luqui


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!
like image 24
3 revs Avatar answered Oct 09 '22 14:10

3 revs


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.

like image 28
madgen Avatar answered Oct 09 '22 13:10

madgen