Scala 集合总数,拒绝不包括所有可能性的类型集合
假设我们有以下类型:Scala 集合总数,拒绝不包括所有可能性的类型集合,scala,haskell,puzzle,type-safety,type-constraints,Scala,Haskell,Puzzle,Type Safety,Type Constraints,假设我们有以下类型: T case对象 case-object-T case-object-T 现在,如何构造T的集合,使每个子T中至少有一个出现在集合中,并在编译时强制执行此约束?与此相反,在以下情况下的集合: val s:Seq[T] = Seq(Goat) 该文件汇编如下: val ts:TotalSeq[T] = TotalSeq(Goat, Goat, Tiger) 没有。我在上面使用过Scala,但我也很乐意看到其他语言的解决方案 (如果我的话有点不对劲,请原谅;我感冒了,今天
T
case对象
case-object-T
case-object-T
现在,如何构造T
的集合,使每个子T
中至少有一个出现在集合中,并在编译时强制执行此约束?与此相反,在以下情况下的集合:
val s:Seq[T] = Seq(Goat)
该文件汇编如下:
val ts:TotalSeq[T] = TotalSeq(Goat, Goat, Tiger)
没有。我在上面使用过Scala,但我也很乐意看到其他语言的解决方案
(如果我的话有点不对劲,请原谅;我感冒了,今天正在用拼图来逗自己。)如果我的解释正确的话
{-# LANGUAGE GADTs #-}
{-# LANGUAGE EmptyDataDecls #-}
data Goat
data Monk
data Tiger
data T a where
TGoat :: T Goat
TMonk :: T Monk
TTiger :: T Tiger
data TSeq a where
TNil :: TSeq ((), (), ())
TG :: T Goat -> TSeq ((), m, t) -> TSeq (Goat, m, t)
TM :: T Monk -> TSeq (g, (), t) -> TSeq (g, Monk, t)
TT :: T Tiger -> TSeq (g, m, ()) -> TSeq (g, m, Tiger)
TCG :: T Goat -> TSeq (Goat, m, t) -> TSeq (Goat, m, t)
TCM :: T Monk -> TSeq (g, Monk, t) -> TSeq (g, Monk, t)
TCT :: T Tiger -> TSeq (g, m, Tiger) -> TSeq (g, m, Tiger)
请注意,尚未尝试使其可用。这是难以置信的笨拙
现在,考虑到这些值:
x = TCG TGoat (TG TGoat (TT TTiger (TM TMonk TNil)))
y = TG TGoat TNil
这个功能是:
f :: TSeq (Goat, Monk, Tiger) -> a
f _ = undefined
…然后只有fx
将进行类型检查,而不是fy
注意:从序列中删除项目留给读者作为练习
编辑:经过进一步考虑,我认为上述情况还不够糟糕。瞧: 一些神秘熟悉的样板:
data Yes = Yes
data No = No
class TypeEq' () x y b => TypeEq x y b | x y -> b
instance TypeEq' () x y b => TypeEq x y b
class TypeEq' q x y b | q x y -> b
class TypeEq'' q x y b | q x y -> b
instance TypeCast b Yes => TypeEq' () x x b
instance TypeEq'' q x y b => TypeEq' q x y b
instance TypeEq'' () x y No
class TypeCast a b | a -> b, b->a where typeCast :: a -> b
class TypeCast' t a b | t a -> b, t b -> a where typeCast' :: t->a->b
class TypeCast'' t a b | t a -> b, t b -> a where typeCast'' :: t->a->b
instance TypeCast' () a b => TypeCast a b where typeCast x = typeCast' () x
instance TypeCast'' t a b => TypeCast' t a b where typeCast' = typeCast''
instance TypeCast'' () a a where typeCast'' _ x = x
有点普通的黑客行为:
infixr 1 :*:
data NIL
data h :*: t = h :*: t
class TIns x ys r | x ys -> r
instance TIns x NIL (x :*: NIL)
instance ( TypeEq x h b
, TIns' b x h t r
) => TIns x (h :*: t) r
class TIns' b x h t r | b x h t -> r
instance TIns' Yes x h t (h :*: r)
instance (TIns x t r) => TIns' No x h t (h :*: r)
class TElem x ys r | x ys -> r
instance TElem x NIL No
instance (TypeEq x h b, TElem' b x h t r) => TElem x (h :*: t) r
class TElem' b x h t r | b x h t -> r
instance TElem' Yes x h t Yes
instance (TElem x t r) => TElem' No x h t r
aa和收益:
data TSeq2 a b where
TNil2 :: TSeq2 NIL NIL
TCons2 :: (TIns x ys r) => T x -> TSeq2 xs ys -> TSeq2 (x :*: xs) r
class (TElem x ys Yes) => Has ys x
instance (TElem x ys Yes) => Has ys x
class HasAll ys xs
instance HasAll ys NIL
instance (ys `Has` h, ys `HasAll` t) => HasAll ys (h :*: t)
x2 = TCons2 TGoat (TCons2 TGoat (TCons2 TTiger (TCons2 TMonk TNil2)))
y2 = TCons2 TGoat TNil2
f2 :: (s `HasAll` (Goat :*: Tiger :*: Monk :*: NIL)) => TSeq2 q s -> a
f2 _ = undefined
如上所述,F2X2
类型检查,而F2Y2
失败
这仍然是混乱和相当痛苦的使用,但更通用
为了证明结果在其他情况下仍然可以作为普通数据类型处理,下面是Show
的一个实例:
instance Show (T a) where
show TGoat = "TGoat"
show TMonk = "TMonk"
show TTiger = "TTiger"
instance Show (TSeq2 a b) where
show TNil2 = "[]"
show (TCons2 x xs) = concat [show x, ":", show xs]
现在我们可以做一些事情,比如只显示包含所有三种项目的序列:
showTotal :: (s `HasAll` (Goat :*: Tiger :*: Monk :*: NIL)) => TSeq2 q s -> String
showTotal = show
它看起来像具有通用类型约束的生成器模式: 比如:
sealed trait TBoolean
sealed trait TTrue extends TBoolean
sealed trait TFalse extends TBoolean
class SeqBuilder[HasGoat <: TBoolean, HasMonk <: TBoolean, HasTiger <: TBoolean] private (seq: Seq[T]) {
def +=(g: Goat.type) = new SeqBuilder[TTrue, HasMonk, HasTiger](g +: seq)
def +=(m: Monk.type) = new SeqBuilder[HasGoat, TTrue, HasTiger](m +: seq)
def +=(t: Tiger.type) = new SeqBuilder[HasGoat, HasMonk, TTrue](t +: seq)
def build()(implicit evg: HasGoat =:= TTrue, evm: HasMonk =:= TTrue, evt: HasTiger =:= TTrue) = seq
}
object SeqBuilder {
def apply = new SeqBuilder(Seq())
}
object SeqBuilder {
val all = Seq(Goat, Monk, Tiger)
def apply(t: T*)(onAll: Seq[T] => Unit)(onViolation: => Unit) = {
val seq = Seq(t:_*)
if(all forall seq.contains) onAll(seq)
else onViolation
}
}
这样,如果没有提供所有必需的Ts,则不会调用函数onAll,否则将调用违例函数我已将其缩减为两个子类,以使其更清晰、更短,但该原则可应用于任意数量的子类。不幸的是,对于许多子类来说,它变得相当长
sealed trait T
case class Monk extends T
case class Tiger extends T
class BaseList(val head:T, val tail:BaseList) {
def ::(m:Monk) = new BaseList(m, this) with ListWithMonk
def ::(t:Tiger) = new BaseList(t, this) with ListWithTiger
}
object Empty extends BaseList(null, null)
trait ListWithMonk extends BaseList {
self:BaseList =>
override def ::(t:Tiger) = new BaseList(t, self) with ListWithBoth
}
trait ListWithTiger extends BaseList {
self:BaseList =>
override def ::(m:Monk) = new BaseList(m, self) with ListWithBoth
}
trait ListWithBoth extends ListWithMonk with ListWithTiger
object Main {
def main(args:Array[String]) {
val l:ListWithBoth = new Monk :: new Tiger :: Empty
val l2:ListWithBoth = new Tiger :: new Monk :: Empty
// does not compile: val l3:ListWithBoth = new Tiger :: new Tiger :: Empty
}
}
你不能完全按照你提出的语法来做,但是你可以接近它;您必须做的唯一折衷是使用运算符而不是,
。我选择了~
,因为在左边没有指定类型时,您没有确切地指定行为,所以我做了一些模糊合理的事情
然而,代码令人头痛。哈斯克尔更善于简明扼要地表达这一点;该策略与camccann已经提供的策略类似
object Example {
sealed trait T
case object Goat extends T
case object Monk extends T
case object Tiger extends T
implicit def Gx(g: Goat.type) = new Goats(Seq(g))
implicit def Mx(m: Monk.type) = new Monks(Seq(m))
implicit def Tx(t: Tiger.type) = new Tigers(Seq(t))
trait Combo { def s: Seq[T] }
case class Goats(s: Seq[T]) extends Combo {
def ~(g: Goat.type) = Goats(s :+ g)
def ~(m: Monk.type) = GoatMonk(s :+ m)
def ~(t: Tiger.type) = GoatTiger(s :+ t)
}
case class Monks(s: Seq[T]) extends Combo {
def ~(g: Goat.type) = GoatMonk(s :+ g)
def ~(m: Monk.type) = Monks(s :+ m)
def ~(t: Tiger.type) = MonkTiger(s :+ t)
}
case class Tigers(s: Seq[T]) extends Combo {
def ~(g: Goat.type) = GoatTiger(s :+ g)
def ~(m: Monk.type) = MonkTiger(s :+ m)
def ~(t: Tiger.type) = Tigers(s :+ t)
}
case class GoatMonk(s: Seq[T]) extends Combo {
def ~(g: Goat.type) = GoatMonk(s :+ g)
def ~(m: Monk.type) = GoatMonk(s :+ m)
def ~(t: Tiger.type) = GoatMonkTiger(s :+ t)
}
case class GoatTiger(s: Seq[T]) extends Combo {
def ~(g: Goat.type) = GoatTiger(s :+ g)
def ~(m: Monk.type) = GoatMonkTiger(s :+ m)
def ~(t: Tiger.type) = GoatTiger(s :+ t)
}
case class MonkTiger(s: Seq[T]) extends Combo {
def ~(g: Goat.type) = GoatMonkTiger(s :+ g)
def ~(m: Monk.type) = MonkTiger(s :+ m)
def ~(t: Tiger.type) = MonkTiger(s :+ t)
}
case class GoatMonkTiger(s: Seq[T]) {
def ~(t: T) = GoatMonkTiger(s :+ t)
}
class TotalSeq[A](val seq: Seq[A]) {}
implicit def total_to_regular[A](ts: TotalSeq[A]) = ts.seq
object TotalSeq {
def apply(t: T) = Seq(t)
def apply(co: Combo) = co.s
def apply(gmt: GoatMonkTiger) = new TotalSeq(gmt.s)
}
}
让我们看看如何使用这个:
scala> import Example._
import Example._
scala> val mmm = TotalSeq(Monk ~ Monk ~ Monk)
mmm: Seq[Example.T] = List(Monk, Monk, Monk)
scala> val mmm2: Seq[T] = TotalSeq(Monk ~ Monk ~ Monk)
mmm2: Seq[Example.T] = List(Monk, Monk, Monk)
scala> val mmm3: TotalSeq[T] = TotalSeq(Monk ~ Monk ~ Monk)
<console>:11: error: type mismatch;
found : Example.Monks
required: Example.GoatMonkTiger
val mmm3: TotalSeq[T] = TotalSeq(Monk ~ Monk ~ Monk)
^
scala> TotalSeq(Tiger ~ Goat ~ Goat ~ Goat)
res5: Seq[Example.T] = List(Tiger, Goat, Goat, Goat)
scala> TotalSeq(Goat ~ Goat ~ Tiger ~ Monk)
res6: Example.TotalSeq[Example.T] = Example$TotalSeq@5568db68
scala> val gmt: TotalSeq[T] = TotalSeq(Goat ~ Goat ~ Tiger ~ Monk)
gmt: Example.TotalSeq[Example.T] = Example$TotalSeq@f312369
scala>导入示例_
导入示例_
scala>val mmm=TotalSeq(Monk~Monk~Monk)
mmm:Seq[Example.T]=列表(Monk,Monk,Monk)
scala>val mmm2:Seq[T]=TotalSeq(Monk~Monk~Monk)
mmm2:Seq[Example.T]=列表(Monk,Monk,Monk)
scala>val mmm3:TotalSeq[T]=TotalSeq(Monk~Monk~Monk)
:11:错误:类型不匹配;
发现:例如,僧侣
必填项:Example.GoatMonkTiger
val mmm3:TotalSeq[T]=TotalSeq(和尚~和尚~和尚)
^
scala>TotalSeq(老虎~山羊~山羊~山羊)
res5:Seq[Example.T]=列表(老虎、山羊、山羊、山羊)
scala>TotalSeq(山羊~山羊~老虎~僧侣)
res6:Example.TotalSeq[Example.T]=示例$TotalSeq@5568db68
scala>val gmt:TotalSeq[T]=TotalSeq(山羊~山羊~老虎~僧侣)
gmt:Example.TotalSeq[Example.T]=示例$TotalSeq@f312369
您真的想说第二个示例不应该编译吗?它至少包含每种T
中的一种。哦,天哪,非常正确。我已经删除了这个和尚。作为一个附录,我可以想出多种方法来推广和/或改进它——这只是一个演示,而不是我在当前形式下使用的东西。实际上,我可以想出一些有用的例子。好吧,如果我认识哈斯克尔的话,我会的。@Rex Kerr:哦,绝对有可能。问题在于这种实用性是否值得增加复杂性和依赖实验性语言扩展。在这种特殊情况下,可能不会。在类型中编码简单数据有效性的方法要简单得多,但它们不符合此处问题的要求。感谢您的全面回答和对原始帖子的编辑。重点是,如果它没有至少一个僧侣、一只老虎和一只山羊,那么它将无法编译。我的解决方案允许重复,我的错误是我没有注意到你没有处理山羊。在您的方法中,您需要一个包含所有三个的列表,一个包含Mon和Goat的列表,一个包含Mon和Tiger的列表,等等……除非我弄错了,否则这个解决方案会产生大量的中间分配?为什么呢??它只是建立了不可变的列表。我很欣赏你的解决方案是如何尽可能接近原始语法的。当然,代码爆炸是一个重大问题;Scala可以很好地与某种宏系统配合使用。@troutwine-Ittay的解决方案可以很容易地进行修改,以便类似地工作。总的来说,这似乎是更好的策略(基本上使用类型系统作为宏系统)。感谢您提供简洁的解决方案和阅读材料。我确实发现我不得不用手去列举T
的总覆盖率,这让我很苦恼,但这只需要做一次。对于终端用户来说,这不是最有吸引力的解决方案,但它非常实用。
scala> import Example._
import Example._
scala> val mmm = TotalSeq(Monk ~ Monk ~ Monk)
mmm: Seq[Example.T] = List(Monk, Monk, Monk)
scala> val mmm2: Seq[T] = TotalSeq(Monk ~ Monk ~ Monk)
mmm2: Seq[Example.T] = List(Monk, Monk, Monk)
scala> val mmm3: TotalSeq[T] = TotalSeq(Monk ~ Monk ~ Monk)
<console>:11: error: type mismatch;
found : Example.Monks
required: Example.GoatMonkTiger
val mmm3: TotalSeq[T] = TotalSeq(Monk ~ Monk ~ Monk)
^
scala> TotalSeq(Tiger ~ Goat ~ Goat ~ Goat)
res5: Seq[Example.T] = List(Tiger, Goat, Goat, Goat)
scala> TotalSeq(Goat ~ Goat ~ Tiger ~ Monk)
res6: Example.TotalSeq[Example.T] = Example$TotalSeq@5568db68
scala> val gmt: TotalSeq[T] = TotalSeq(Goat ~ Goat ~ Tiger ~ Monk)
gmt: Example.TotalSeq[Example.T] = Example$TotalSeq@f312369