- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
在 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]
我知道我应该将功能抽象出来并将它们放在一个列表中,但在编写代码的过程中我经常感到受阻。在这些情况下我应该有什么总体心态?
最佳答案
您正在编写一些出色的代码。让我将它再推向类似 Haskell 的解决方案。
您已成功将每个Piece
建模为独立实体。这看起来完全没问题,但您希望能够使用片段集合。最直接的方法是描述一个类型,它可以是任何所需的部分。
data Piece = PipePiece Pipe
| TankPiece Tank
| BowlPiece Bowl
| CrossPiece Cross
| SourcePiece Source
它可以让你写一个像这样的列表
type Kit = [Piece]
但要求在使用 Kit
时,在不同类型的 Piece
上进行模式匹配
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
这消除了许多冗余的类型构造函数,但代价是不再能够反射(reflect)函数类型中的片段类型 - 我们不再可以编写
rotateBowl :: Bowl -> Bowl
rotateBowl (Bowl orientation) = Bowl (rotate orientation)
但是相反
rotateBowl :: Piece -> Piece
rotateBowl (Bowl orientation) = Bowl (rotate orientation)
rotateBowl somethingElse = somethingElse
这很烦人。
希望这能突显这两种模型之间的一些权衡。至少有一种“更奇特”的解决方案,它使用类型类和 ExistentialQuantification 来“忘记”除接口(interface)之外的所有内容。这是值得探索的,因为它很诱人,但被认为是 Haskell 反模式。我先描述一下,然后再讨论更好的解决方案。
要使用ExistentialQuantification
,我们删除总和类型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
会导致更复杂的类型错误并删除大量有趣的信息。通常的论点是,如果你要删除除了熔化
碎片
之外的所有信息,你应该一开始就熔化它。
myScrapMetal :: [ScrapMetal]
myScrapMetal = [melt Cross, melt Source Vertical]
如果您的类型类具有多个函数,那么您真正的功能可能存储在该类中。例如,假设我们可以熔化
一 block 碎片
并出售
它,也许更好的抽象如下
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
的类型类字典——这正是类型类中函数的产物,这就是我们的使用刚刚描述的数据 block
类型重新显式建模。
我能想到使用 ExistentialQuantification
的一个原因是 Haskell 的 Exception
系统最好的例证 - 如果您有兴趣,请看一下它是如何实现的。缺点是必须设计Exception
,以便任何人都可以在任何代码中添加新的Exception
,并使其可通过共享 Control.Exception
机制,同时保持足够的身份以便用户捕获它。这还需要 Typeable
机制......但这几乎肯定是矫枉过正。
要点应该是,您使用的模型在很大程度上取决于您最终如何使用数据类型。将所有内容表示为抽象 ADT(例如数据片解决方案)的初始编码很好,因为它们会丢弃很少的信息……但也可能既笨重又缓慢。像 melt
/sell
字典这样的最终编码通常更高效,但需要更深入地了解 Piece
的“含义”以及它的含义使用过。
关于oop - 在 Haskell 中编程时如何纠正我的 OOP 倾向,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18867620/
在 Haskell 中编程时,我经常遇到这个问题。在某些时候,我尝试模拟 OOP 方法。在这里,我正在为我发现的一款 Flash 游戏编写某种 AI,我想将各个部分和关卡描述为一个部分列表。 modu
我是一名优秀的程序员,十分优秀!