med*_*ans 13 oop haskell functional-programming
在Haskell中编程时,我遇到了这个经常出现的问题.在某些时候,我尝试模拟OOP方法.在这里,我为我发现的flash游戏编写了一些AI,我想将各个部分和级别描述为一个部分列表.
module Main where
type Dimension = (Int, Int)
type Position = (Int, Int)
data Orientation = OrienLeft | OrienRight
data Pipe = Vertical | Horizontal | UpLeft | UpRight | DownLeft | DownRight
data Tank = Tank Dimension Orientation
data Bowl = Bowl Dimension
data Cross = Cross
data Source = Source Dimension
-- desired
-- data Piece = Pipe | Tank | Bowl | Cross | Source
-- So that I can put them in a list, and define
-- data Level = [Piece]
Run Code Online (Sandbox Code Playgroud)
我知道我应该抽象出功能并将它们放在一个列表中,但我常常在编写代码的过程中感到被阻止.在这些情况下我应该有什么样的心态?
J. *_*son 15
你正在寻找一些优秀的代码.让我再向一个类似Haskell的解决方案推进几步.
您已成功将每个模型建模Piece为独立实体.这看起来完全没问题,但您希望能够处理各个部分的集合.最直接的方法是描述一种类型,它可以是任何所需的部分.
data Piece = PipePiece Pipe
| TankPiece Tank
| BowlPiece Bowl
| CrossPiece Cross
| SourcePiece Source
Run Code Online (Sandbox Code Playgroud)
这会让你写一些像这样的作品
type Kit = [Piece]
Run Code Online (Sandbox Code Playgroud)
但要求当你消费你的时Kit,你就可以在不同类型的Pieces 上进行模式匹配
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
这消除了许多冗余类型构造函数,代价是不再能够反映函数类型中的哪种类型 - 我们不再能够编写
rotateBowl :: Bowl -> Bowl
rotateBowl (Bowl orientation) = Bowl (rotate orientation)
Run Code Online (Sandbox Code Playgroud)
但反而
rotateBowl :: Piece -> Piece
rotateBowl (Bowl orientation) = Bowl (rotate orientation)
rotateBowl somethingElse = somethingElse
Run Code Online (Sandbox Code Playgroud)
这很烦人.
希望这突出了这两个模型之间的一些权衡.至少有一个"更具异国情调"的解决方案,它使用类型类并ExistentialQuantification"忘记"除界面之外的所有内容.这是值得探索的,因为它非常诱人,但被认为是一个Haskell反模式.我先描述一下然后谈谈更好的解决方案.
要使用ExistentialQuantification我们删除sum类型Piece并为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
Run Code Online (Sandbox Code Playgroud)
这是一个反模式,因为ExistentialQuantification会导致更复杂的类型错误并删除大量有趣的信息.通常的理由是,如果你要删除除了能力的所有信息melt的Piece,你应该刚刚融化它开始.
myScrapMetal :: [ScrapMetal]
myScrapMetal = [melt Cross, melt Source Vertical]
Run Code Online (Sandbox Code Playgroud)
如果您的类型类具有多个函数,那么您的实际功能可能存储在该类中.举例来说,假设我们可以melt一个piece,也sell它,也许是更好的抽象会是以下
data Piece = { melt :: ScrapMetal
, sell :: Int
}
pipe :: Direction -> Piece
pipe _ = Piece someScrap 2.50
myKit :: [Piece]
myKit = [pipe UpLeft, pipe UpRight]
Run Code Online (Sandbox Code Playgroud)
老实说,这几乎就是你通过这种ExistentialQuantification方法获得的,但更直接.当你删除类型信息时,forgetPiece只留下类型类词典class Piece- 这正是类型类中函数的一个产物,这是我们用data Piece刚才描述的类型明确建模的.
我可以想到使用的一个原因ExistentialQuantification最好的例子是Haskell的Exception系统 - 如果你有兴趣,看看它是如何实现的.缺点是Exception必须设计成任何人都可以Exception在任何代码中添加新内容并使其可以通过共享 Control.Exception机器路由,同时保持足够的身份以便用户捕获它.这也需要Typeable机械......但它几乎肯定是矫枉过正的.
需要注意的是,您使用的模型在很大程度上取决于您最终消耗数据类型的方式.初始编码,你把所有东西都表示为一个抽象的ADT,就像data Piece解决方案一样好,因为它们丢弃了很少的信息......但也可能既笨拙又慢.像melt/ selldictionary 这样的最终编码通常更有效,但需要更深入地了解Piece"手段"及其使用方式.
您正在考虑多态性。Haskell 中也有这样的地方,只是做法不同。
例如,您似乎想以通用方式处理关卡中的片段。那是什么处理?如果您可以定义这些函数,您会发现这就像定义一个 Piece 接口一样。在 Haskell 中,这将是一个类型类(定义为class Piece a“实现”应该处理的函数列表)。
然后,您需要定义这些函数对特定数据类型的作用,例如instance Piece Pipe并添加这些函数的定义。对所有数据类型完成此操作后,您可以将它们添加到片段列表中。