我正在寻找将ADT与显式子类型结合起来的数据类型的正确名称。
在我的一个应用程序中,我使用类似于ADT的结构来表示解析树,我在其上执行递归模式匹配。如果我能将ADT与子类型结合起来,我觉得相当方便,如下例所示:
注意:该示例是用Haskell的语法编写的,但这不是Haskell代码。
data Empty = Empty
data Expr = Int Int | Add Expr AddOp Expr
data OptionalExpr =
| Empty // I want to make Empty a subtype of OptionalExpr
| Expr // I want to make Expr a subtype of OptionalExpr
在上面的例子中,我首先定义了两种类型:Empty和Expr。然后我将这两种类型作为OptionalExpr的子类型。我意识到这种数据类型是 uncommon 。显然,Haskell和OCaml都不支持它。但我不了解其他功能语言。
我正在寻找将ADT与显式子类型相结合的东西,而不是像多态变体那样结构暗示的子类型。这个要求有一些理由:
data OptionalExpr = EmptyExpr | NonEmptyExpr Expr
,或者我们可以使用Maybe
,但在我的应用程序中这是不可接受的,因为级别嵌入可能非常深,解构一个深层嵌入的值如(L1 (L2 (L3 (L4 (L5 value_wanted)))))
将是一场噩梦。为了让您了解为何存在此类要求,我将在下面展示一个更具体的示例:
PrimaryExpr = ID | LeftParen Expr RightParen
UnaryExpr = PrimaryExpr | NegateOp PrimaryExpr // -
MultExpr = UnaryExpr | MultExpr MultOp UnaryExpr // *
AddExpr = MultExpr | AddExpr AddOp MultExpr // +
CompExpr = AddExpr | AddExpr CompOp AddExpr
Expr = CompExpr
上面的示例表示子类型层次结构,并表示诸如AddExpr之类的想法是CompExpr,但CompExpr不是AddExpr。对于这个具体的例子,有些人向我建议我可以用Expr替换UnaryExpr,MultExpr,AddExpr等。也就是说,我可以将所有类型定义为单一类型。丢失类型约束,例如CompExpr不是AddExpr,因为我在这些类型上进行递归模式匹配,我需要静态强制执行此层次结构的约束。
我在文献中寻找的这种数据类型是否有名称?或者我在寻找一些甚至没有意义的东西?如果您认为是这种情况,为什么我要找一些荒谬的东西?谢谢你的任何指示。
编辑:尽管我已经用Haskell的语法编写了上面的代码片段,但我没有在Haskell中编写我的应用程序。我使用自己的语言和自己的数据类型,因此我不受Haskell语义的限制。我正在寻找指向文学中类似概念的指针,因此当我为我的项目撰写报告时,我似乎并没有重新发明新的东西。我尝试了所有我能想到的谷歌关键词,没有任何权利被归还,所以我在这里问。答案 0 :(得分:3)
在评论中,您说:
我不确定如何使用GADT对子类型层次结构进行编码。如果您认为它是可行的,您是否介意提供一个答案,并举例说明我的示例中给出的类型层次结构如何编码?
因此我在这里回答这个问题。关键的想法是给出一个类型级函数(在宿主语言中,这里是Haskell)来计算子类型关系(目标语言的类型系统,这里是你的自定义EDSL)。为简单起见,我将详细说明子类型关系,但标准类型级编程可用于减少重复并适当提高抽象级别。首先,需要扩展:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
现在定义子类型关系:
data Level = Primary | Unary | Mult | Add | Comp
type family Subtype a b where
Subtype Primary a = True
Subtype Unary Primary = False
Subtype Unary a = True
Subtype Mult Primary = False
Subtype Mult Unary = False
Subtype Mult a = True
Subtype Add Add = True
Subtype Add Comp = True
Subtype Add a = False
Subtype Comp Comp = True
Subtype Comp a = False
封闭类型系列用于保证客户端(您的第二个属性)无法扩展子类型关系。最后,目标语言术语的GADT可以使用子类型关系作为其构造函数的约束。
data Expr a where
ID :: Subtype Primary a ~ True => Expr a
Paren :: Subtype Primary a ~ True => Expr b -> Expr a
Negate :: Subtype Unary a ~ True => Expr Unary -> Expr a
Times :: Subtype Add a ~ True => Expr Mult -> Expr Mult -> Expr a
Plus :: Subtype Add a ~ True => Expr Add -> Expr Add -> Expr a
Compose :: Subtype Comp a ~ True => Expr Comp -> Expr Comp -> Expr a
请注意,因为Paren
的参数是多态的,所以您需要在包含的术语上使用类型注释来表示您希望该术语被视为的子类型层次结构的“级别”。我希望你也需要用你设计的任何语言来做这件事。在ghci中,我们可以询问样本术语的类型:
:t Compose (Times ID ID) (Negate (Paren (Plus ID ID :: Expr Add)))
Compose (Times ID ID) (Negate (Paren (Plus ID ID :: Expr Add)))
:: (Subtype 'Comp a ~ 'True) => Expr a
我认为这或多或少是你对这个词期望的类型。您还可以看到表达式层次结构是严格执行的,但我敢说错误消息不是100%明确的(因为它是用宿主语言术语而不是目标语言术语编写的):
:t Negate (Plus ID ID)
<interactive>:1:9:
Couldn't match type ‘'False’ with ‘'True’
Expected type: 'True
Actual type: Subtype 'Add 'Unary
In the first argument of ‘Negate’, namely ‘(Plus ID ID)’
In the expression: Negate (Plus ID ID)
答案 1 :(得分:2)
我想到了两件事,包括&#34;真实&#34;子类型系统(因此在Haskell中不可用),虽然我不完全确定它们是否符合您的所有要求:
明确无标记union types,与Ceylon一样,可让您将类型命名为A | B
,这是A
和B
的超类型。因此,您只需设置Empty
和Expr
正常ADT,然后声明同义词type OptionalExpr = Empty | Expr
。
ADT在Scala中建模的方式,作为密封特征和案例类的层次结构:
sealed trait OptionalExpr
case object Empty extends OptionalExpr
sealed trait Expr extends OptionalExpr
case class IntExpr(i: Int) extends OptionaExpr
case class AddExpr(lhs: Expr, op: AddOp, rhs: Expr) extends OptionalExpr
这样,OptionalExpr
和Expr
不可扩展(因为特征是密封的),并且在Haskell中的行为大致类似于ADT,但您仍然可以访问&#34;中间&#34;像普通继承层次结构中的类型(与Haskell不同,在Haskell中只有构造函数,它们本身不是类型)。
当然,这两种情况都需要一种模式匹配的形式来访问值,因为你必须恢复工会的哪一部分&#34;你是。
答案 2 :(得分:2)
Haskell特别适合对您的域进行建模,可能是因为它可以用相当简单的数学模型来描述。至关重要的是,您的第一点意味着子类型关系是well-order。这使你的生活变得非常简单 - 这种模式可能很容易转换为任何类型系统至少与Haskell一样强大的语言。
首先定义一个类型(将被提升到一种类型)来表示你的变体:
data Variant = Primary | Unary | Mult | Add | Comp | Expr
接下来使用非 - 递归数据类型来表示术语中的节点:
data ExprF (k :: Variant -> *) (x :: Variant) where
ID_F :: ExprF k 'Primary
Paren_F :: k 'Expr -> ExprF k 'Primary
Negate_F :: k 'Primary -> ExprF k 'Unary
Mult_F :: k 'Mult -> k 'Unary -> ExprF k 'Mult
Add_F :: k 'Add -> k 'Mult -> ExprF k 'Add
Comp_F :: k 'Add -> k 'Add -> ExprF k 'Comp
术语的递归出现由附加参数表示。本质上,这只是典型的多项式仿函数表示(即Fix
),但具有索引参数。
您的表达式类型是:
data Expr' (x :: Variant) where
Expr' :: (x <= y) => Expr x -> Expr' y
data Expr (x :: Variant) where
MkExpr :: ExprF Expr' x -> Expr x
尚未引入<=
类,但它代表您的子类型关系。
如前所述,您的子类型关系是一个良好的顺序,并且通过这种方式,可以为排序中的每个元素分配一个唯一的自然数,以便自然的典型排序尊重您的子类型关系。或者换句话说,有一个注入f : Variant -> Nat
,x
是y
iff f x <= f y
的子类型(或严格的子类型iff f x < f y
- 这样的表示给你很多一般性)。
所需的注射只是由你的语法给出。注意,每个产品只是它上面的产品的“子类型”(即,具有不应该引入构造函数的右侧)。
data Nat = Z | S Nat
infixr 0 $
type ($) f a = f a
type family VariantIx (x :: Variant) :: Nat where
VariantIx 'Primary = 'Z
VariantIx 'Unary = 'S 'Z
VariantIx 'Mult = 'S $ 'S 'Z
VariantIx 'Add = 'S $ 'S $ 'S 'Z
VariantIx 'Comp = 'S $ 'S $ 'S $ 'S 'Z
VariantIx 'Expr = 'S $ 'S $ 'S $ 'S $ 'S 'Z
您需要一个隐式子类型关系(<=
),但使用关系的显式证明通常要容易得多,因此隐式版本通常只生成显式证明。为此,您要编写两个声明:
data family (:<=:) (x :: k) (y :: k)
class (<=) (x :: k) (y :: k) where
isLTEQ :: x :<=: y
自然的实例应该相当明显:
data instance (:<=:) (x :: Nat) y where
LT_Z :: 'Z :<=: n
LT_S :: n :<=: m -> 'S n :<=: 'S m
instance 'Z <= n where isLTEQ = LT_Z
instance (n <= m) => 'S n <= 'S m where isLTEQ = LT_S isLTEQ
Variant
的实例定义了VariantIx
:
newtype instance (:<=:) (x :: Variant) y = IsSubtype (VariantIx x :<=: VariantIx y)
instance (VariantIx x <= VariantIx y) => x <= y where isLTEQ = IsSubtype isLTEQ
您可能想要一些智能构造函数。如果您使用最近的GHC,您将可以访问模式同义词,但没有必要:
id_ = MkExpr ID_F
pattern Id = MkExpr ID_F
pattern Paren e = MkExpr (Paren_F (Expr' e))
pattern Neg e = MkExpr (Negate_F (Expr' e))
infixl 6 :+
pattern (:+) a b = MkExpr (Add_F (Expr' a) (Expr' b))
infixl 7 :*
pattern (:*) a b = MkExpr (Mult_F (Expr' a) (Expr' b))
pattern Cmp a b = MkExpr (Comp_F (Expr' a) (Expr' b))
和一些简单的例子:
>Id :+ Id :+ Neg Id :* Id
Add_F (Add_F ID_F ID_F) (Mult_F (Negate_F ID_F) ID_F)
>Id :+ Id :* Neg (Id :* Id)
<interactive>:6:13:
No instance for (('S $ 'S 'Z) <= 'Z) arising from a use of `Neg'
请注意,您也可以用稍微不同的方式编写表达式类型:
data ExprFlip (x :: Variant) where
MkExprFlip :: (x <= y) => ExprF ExprFlip x -> ExprFlip y
这与原文的不同之处在于表达式的最外层类型具有应用于它的子类型关系 - 例如。
pattern Id' = MkExprFlip ID_F
在ExprFlip t
时有Id :: Expr 'Primary
类型。我看不出他们有什么不同的其他方式,我想这只是一个偏好问题,或者哪些用例最常见。原始表示具有以下优点:输出类型始终是单形的,这在某些情况下可以使类型推断更好,但不会影响表达式的构造。
解决你的四点:
VariantIx
并且Variant
类型已关闭。 :<=:
或<=
的{{1}}或Variant
的任何其他实例将与现有的实例(尽可能一般)重叠,因此原则上他们可以定义,尝试使用它们会产生类型错误。Nat
<=
实例中一劳永逸地捕获这些属性。更改子类型关系仅限于更改Nat
和Variant
。VariantIx
类构成。由于<=
数据类型中的所有索引都是单态的,因此类型检查器将始终能够计算索引的子类型关系。完整代码:
ExprF
答案 3 :(得分:1)
除非我误解,否则多态变体几乎可以做到这一点。然而,“没有标记的联盟”并不是一个很好用的术语(我想大多数人会认为你要求的是C式联盟)。
示例如下所示:
type empty = [`Empty]
type bin_op = Add | Sub
type expr = [`Int of int | `Add of expr * bin_op * expr]
type optional_expr = [empty | expr]
type weird_expr = [expr | `Wierd of expr | `Zonk of string]
请注意,对于OCaml的多态变体,子类型关系是在结构上定义的,而不是在命名类型之间定义。