表达无限种
Expressing infinite kinds
在Haskell中表达无限类型时:
f x = x x -- This doesn't type check
可以使用 newtype
来完成:
newtype Inf = Inf { runInf :: Inf -> * }
f x = x (Inf x)
是否存在允许表达无限种类的newtype
种类等价物?
我已经发现我可以使用类型族来获得类似的东西:
{-# LANGUAGE TypeFamilies #-}
data Inf (x :: * -> *) = Inf
type family RunInf x :: * -> *
type instance RunInf (Inf x) = x
但我对这个解决方案不满意 - 与等效类型不同,Inf
不会创建新种类(Inf x
有种类 *
),所以有不太安全。
这个问题有更优雅的解决方案吗?
我认为您正在寻找 Fix
,它被定义为
data Fix f = Fix (f (Fix f))
Fix
为您提供 f
类型的定点。我不确定您要做什么,但是当您使用递归方案(您可以使用的递归模式)时,通常会使用这种无限类型,请参阅 Edward Kmett 的 recursion-schemes
包或使用免费的单子,其中包括things 允许您以单子样式构建 AST。
回应:
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
我有一个方法,可能接近您的要求,我已经 experimenting with。它似乎非常强大,尽管围绕这个结构的抽象需要一些开发。关键是有一种 Label
表示递归将从哪里继续。
{-# LANGUAGE DataKinds #-}
import Data.Kind (Type)
data Label = Label ((Label -> Type) -> Type)
type L = 'Label
L
只是构造标签的捷径。
开定点定义属于 (Label -> Type) -> Type
类型,也就是说,它们采用 "label interpretation (type) function" 并返回一个类型。我称这些为"shape functors",并用字母h
抽象地指代它们。最简单的形状函子是不递归的:
newtype LiteralF a f = Literal a -- does not depend on the interpretation f
type Literal a = L (LiteralF a)
现在我们可以做一个小的表达式语法作为例子:
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))
注意我们如何将 labels 传递给 f
,后者负责关闭递归。如果我们只想要一个普通的表达式树,我们可以使用 Tree
:
data Tree :: Label -> Type where
Tree :: h Tree -> Tree (L h)
然后 Tree (L Expr)
与您期望的正常表达式树同构。但这也允许我们,例如,在树的每一层用标签相关的注释来注释树:
data Annotated :: (Label -> Type) -> Label -> Type where
Annotated :: ann (L h) -> h (Annotated ann) -> Annotated ann (L h)
在回购中 ann
由形状仿函数而不是标签索引,但现在对我来说这似乎更清晰。有很多像这样的小决定要做出,我还没有找到最方便的模式。围绕形函子使用的最佳抽象仍然需要探索和开发。
还有很多其他的可能性,其中很多我还没有探索过。如果您最终使用它,我很想听听您的用例。
对于数据种类,我们可以使用常规的新类型:
import Data.Kind (Type)
newtype Inf = Inf (Inf -> Type)
并推广它(使用 '
)以创建新种类来表示循环:
{-# LANGUAGE DataKinds #-}
type F x = x ('Inf x)
对于解压其 'Inf
参数的类型,我们需要一个类型系列:
{-# LANGUAGE TypeFamilies #-}
type family RunInf (x :: Inf) :: Inf -> Type
type instance RunInf ('Inf x) = x
现在我们可以用新的种类来表达无限种类,所以不会丢失种类安全。
- 感谢@luqui 指出他回答中的
DataKinds
部分!
在Haskell中表达无限类型时:
f x = x x -- This doesn't type check
可以使用 newtype
来完成:
newtype Inf = Inf { runInf :: Inf -> * }
f x = x (Inf x)
是否存在允许表达无限种类的newtype
种类等价物?
我已经发现我可以使用类型族来获得类似的东西:
{-# LANGUAGE TypeFamilies #-}
data Inf (x :: * -> *) = Inf
type family RunInf x :: * -> *
type instance RunInf (Inf x) = x
但我对这个解决方案不满意 - 与等效类型不同,Inf
不会创建新种类(Inf x
有种类 *
),所以有不太安全。
这个问题有更优雅的解决方案吗?
我认为您正在寻找 Fix
,它被定义为
data Fix f = Fix (f (Fix f))
Fix
为您提供 f
类型的定点。我不确定您要做什么,但是当您使用递归方案(您可以使用的递归模式)时,通常会使用这种无限类型,请参阅 Edward Kmett 的 recursion-schemes
包或使用免费的单子,其中包括things 允许您以单子样式构建 AST。
回应:
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
我有一个方法,可能接近您的要求,我已经 experimenting with。它似乎非常强大,尽管围绕这个结构的抽象需要一些开发。关键是有一种 Label
表示递归将从哪里继续。
{-# LANGUAGE DataKinds #-}
import Data.Kind (Type)
data Label = Label ((Label -> Type) -> Type)
type L = 'Label
L
只是构造标签的捷径。
开定点定义属于 (Label -> Type) -> Type
类型,也就是说,它们采用 "label interpretation (type) function" 并返回一个类型。我称这些为"shape functors",并用字母h
抽象地指代它们。最简单的形状函子是不递归的:
newtype LiteralF a f = Literal a -- does not depend on the interpretation f
type Literal a = L (LiteralF a)
现在我们可以做一个小的表达式语法作为例子:
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))
注意我们如何将 labels 传递给 f
,后者负责关闭递归。如果我们只想要一个普通的表达式树,我们可以使用 Tree
:
data Tree :: Label -> Type where
Tree :: h Tree -> Tree (L h)
然后 Tree (L Expr)
与您期望的正常表达式树同构。但这也允许我们,例如,在树的每一层用标签相关的注释来注释树:
data Annotated :: (Label -> Type) -> Label -> Type where
Annotated :: ann (L h) -> h (Annotated ann) -> Annotated ann (L h)
在回购中 ann
由形状仿函数而不是标签索引,但现在对我来说这似乎更清晰。有很多像这样的小决定要做出,我还没有找到最方便的模式。围绕形函子使用的最佳抽象仍然需要探索和开发。
还有很多其他的可能性,其中很多我还没有探索过。如果您最终使用它,我很想听听您的用例。
对于数据种类,我们可以使用常规的新类型:
import Data.Kind (Type)
newtype Inf = Inf (Inf -> Type)
并推广它(使用 '
)以创建新种类来表示循环:
{-# LANGUAGE DataKinds #-}
type F x = x ('Inf x)
对于解压其 'Inf
参数的类型,我们需要一个类型系列:
{-# LANGUAGE TypeFamilies #-}
type family RunInf (x :: Inf) :: Inf -> Type
type instance RunInf ('Inf x) = x
现在我们可以用新的种类来表达无限种类,所以不会丢失种类安全。
- 感谢@luqui 指出他回答中的
DataKinds
部分!