您正在编写一些很棒的代码。让我将它推向类似 Haskell 的解决方案。
您已经成功地将每一个建模Piece
为一个独立的实体。这看起来完全没问题,但是您希望能够处理碎片集合。最直接的方法是描述可以是任何所需部分的类型。
data Piece = PipePiece Pipe
| TankPiece Tank
| BowlPiece Bowl
| CrossPiece Cross
| SourcePiece Source
这会让你写一个像这样的作品列表
type Kit = [Piece]
但要求当您使用您的时Kit
,您在不同类型的Piece
s上进行模式匹配
instance Show Piece where
show (PipePiece Pipe) = "Pipe"
show (TankPiece Tank) = "Tank"
show (BowlPiece Bowl) = "Bowl"
show (CrossPiece Cross) = "Cross"
show (SourcePiece Source) = "Source"
showKit :: Kit -> String
showKit = concat . map show
还有一个强有力的论据是Piece
通过“扁平化”一些冗余信息来降低类型的复杂性
type Dimension = (Int, Int)
type Position = (Int, Int)
data Orientation = OrienLeft | OrienRight
data Direction = Vertical | Horizontal | UpLeft | UpRight | DownLeft | DownRight
data Piece = Pipe Direction
| Tank Dimension Orientation
| Bowl Dimension
| Cross
| Source Dimension
它消除了许多冗余的类型构造函数,代价是不再能够反映函数类型中的类型——我们不再可以编写
rotateBowl :: Bowl -> Bowl
rotateBowl (Bowl orientation) = Bowl (rotate orientation)
但反而
rotateBowl :: Piece -> Piece
rotateBowl (Bowl orientation) = Bowl (rotate orientation)
rotateBowl somethingElse = somethingElse
这很烦人。
希望这能突出这两种模型之间的一些权衡。至少有一个“更奇特”的解决方案,它使用类型类并ExistentialQuantification
“忘记”除了接口之外的所有内容。这是值得探索的,因为它很诱人,但被认为是 Haskell 反模式。我会先描述它,然后再讨论更好的解决方案。
要使用ExistentialQuantification
,我们删除 sum 类型Piece
并为碎片创建一个类型类。
{-# LANGUAGE ExistentialQuantification #-}
class Piece p where
melt :: p -> ScrapMetal
instance Piece Pipe
instance Piece Bowl
instance ...
data SomePiece = forall p . Piece p => SomePiece p
instance Piece SomePiece where
melt (SomePiece p) = melt p
forgetPiece :: Piece p => p -> SomePiece
forgetPiece = SomePiece
type Kit = [SomePiece]
meltKit :: Kit -> SomePiece
meltKit = combineScraps . map melt
这是一种反模式,因为ExistentialQuantification
会导致更复杂的类型错误和大量有趣信息的擦除。通常的论点是,如果您要擦除除 的能力之外的所有信息melt
,Piece
您应该从一开始就将其融化。
myScrapMetal :: [ScrapMetal]
myScrapMetal = [melt Cross, melt Source Vertical]
如果您的类型类具有多个功能,那么您的真正功能可能存储在该类中。例如,假设我们可以melt
apiece
和sell
它,也许更好的抽象是以下
data Piece = { melt :: ScrapMetal
, sell :: Int
}
pipe :: Direction -> Piece
pipe _ = Piece someScrap 2.50
myKit :: [Piece]
myKit = [pipe UpLeft, pipe UpRight]
老实说,这几乎正是您通过该ExistentialQuantification
方法获得的结果,但更直接。当您通过删除类型信息时,forgetPiece
只留下类型类字典class Piece
--- 这正是类型类中函数的产物,这是我们使用data Piece
刚刚描述的类型显式建模的内容。
我能想到使用的一个原因ExistentialQuantification
是 Haskell 的系统最好的例证Exception
——如果你有兴趣,看看它是如何实现的。简而言之,它的Exception
设计必须使任何人都可以Exception
在任何代码中添加新代码,并使其可通过共享 Control.Exception
机器进行路由,同时保持足够的身份让用户也能捕捉到它。这Typeable
也需要机器……但这几乎可以肯定是矫枉过正。
外卖应该是您使用的模型将很大程度上取决于您最终如何使用您的数据类型。将所有内容表示为抽象 ADT(如data Piece
解决方案)的初始编码很好,因为它们丢弃的信息很少……但也可能既笨拙又缓慢。melt
/字典之类的最终编码sell
通常更有效,但需要更深入地了解Piece
“含义”以及如何使用它。