【问题标题】:Identity of simulation objects in HaskellHaskell 中模拟对象的标识
【发布时间】:2013-10-07 16:09:49
【问题描述】:

使用面向对象的语言编写模拟,每个对象都有一个标识——也就是说,一种将其与模拟中的所有其他对象区分开来的方法,即使其他对象具有完全相同的属性。一个对象保留它的身份,无论它随着时间的推移发生多少变化。这是因为每个对象在内存中都有一个唯一的位置,我们可以用指针或引用来表达那个位置。即使您不强加像 GUID 这样的附加身份系统,这也有效。 (您经常会这样做以支持不考虑指针的网络或数据库之类的东西。)

我认为 Haskell 中没有等效的概念。那么,标准方法是使用 GUID 之类的东西吗?

更新以澄清问题:身份是我的问题域中的一个重要概念,原因有一个:对象之间存在关系,必须保留这些关系。例如,Haskell 通常会说红色汽车是红色汽车,并且所有红色汽车都是相同的(假设颜色是汽车的唯一属性)。但是,如果每辆红色汽车都必须与它的主人联系起来呢?如果车主可以重新粉刷他的汽车呢?

综合答案的最终更新: 共识似乎是,只有在模拟的某些部分实际使用这些标识符时,您才应该向数据类型添加标识符,并且没有其他方式来表达相同的信息。例如。对于一个人拥有多个汽车的情况,每个汽车都有一个颜色,一个人可以保留一个不可变汽车的列表。只要您有权访问 Person,这就充分表达了所有权关系。

Person 可能需要也可能不需要某种唯一标识符。发生这种情况的一种情况是:有一个函数接受 Car 和所有 Person 的集合,并将 ParkingTicket 强加给适当的 Person。汽车的颜色不能唯一地识别获得票的人。所以我们可以给 Person 一个 ID 并让 Car 存储它。

但即使是这种情况,也可以通过更好的设计来避免。也许我们的汽车现在有一个类型为 ParkingPosition 的附加属性,可以将其评估为合法或非法。因此,我们将 Person 集合传递给一个函数,该函数查看每个 Person 的 Cars 列表,检查每个人的 ParkingPosition,并在适当的情况下将 ParkingTicket 强加给那个 Person。因为这个函数总是知道它在看哪个 Person,所以 Car 不需要记录这些信息。

因此,在许多情况下,分配 ID 并不像最初看起来那样必要。

【问题讨论】:

  • 您能详细说明您要达到的目标吗?当您的对象是可变的时,对象标识显然很有用,但是为什么要在 Haskell 中区分相同的值呢?
  • 因为有时这些对象与其他对象有关系(如所有权),并且必须保留这些关系。例如。两个不同的发射器可能“拥有”两个射弹。这些射弹需要某种方式来跟踪谁拥有它们。他们不能只拥有指向其所有者的指针,因此需要不同的策略。
  • @Jarret 为什么你不能通过它们的启动器来区分它们?你还需要什么?
  • @Jarrett,我更新了我的答案

标签: haskell


【解决方案1】:

为什么要“解决”这个非问题?对象标识是 OO 语言的一个问题,Haskell 很乐意避免。

在不可变对象的世界中,具有相同值的两个对象同一个对象。将相同的不可变对象两次放入列表中,无论您想以这种方式查看事物的任何地方都有两个不同的对象(例如,它们“都”有助于元素的总数,并且它们具有唯一的索引)而没有任何问题Java 风格的引用相等导致。如果您愿意,您甚至可以将该列表保存到数据库表中并获得两个不同的行。你还需要什么?

更新

Jarret,您似乎确信模型中的对象必须具有真正独立的身份,因为现实生活中的对象是不同的。但是,在模拟中,这仅在需要区分对象的上下文中才重要。通常,您只需要在必须区分和跟踪对象的上下文中使用唯一标识符,而不需要在这些上下文之外。因此,确定这些上下文,将重要的对象的生命周期映射到您的模拟(而不是“真实”世界),并创建适当的标识符。

您不断为自己的问题提供答案。如果汽车有所有者,那么鲍勃的红色汽车可以与卡罗尔的红色汽车区分开来。如果鲍勃可以重新粉刷他的车,您可以用蓝色车替换他的红色车。你只需要更多,如果

  1. 您的模拟中有没有车主的汽车
  2. 您需要能够区分一辆无主的红色汽车和另一辆。

在一个简单的模型中,1 可能是真的,2 不是。在这种情况下,所有无主的红色汽车都是同一辆红色汽车那么为什么要费心将它们区分出来呢?

在您的导弹模拟中,为什么导弹需要跟踪自己的发射器?他们的目标不是他们的发射器!如果发射器在发射后还能继续控制导弹,那么发射器需要知道自己拥有哪些导弹,反之则不然。导弹只需要知道它的轨迹和目标。当它落地并爆炸时,拥有者的意义何在?如果从发射器 A 而不是发射器 B 发射,它会产生更大的爆炸吗?

您的发射器可以是空的,也可以有n 导弹仍然可以发射。它有一个位置。目标有位置。任何时候都有k导弹在飞行;每枚导弹都有一个位置、一个速度/轨迹和一个爆炸力。任何位置与地面重合的导弹都应该变成爆炸导弹,或者哑弹等等。

在每种情况下,哪些信息很重要?发射器身份在引爆时真的重要吗?为什么?敌人会不会发动报复性打击?不?那么这对于爆炸来说不是重要的信息。 启动之后,它甚至可能不是重要的信息。发射可以简单地是一个步骤,其中属于发射器 A 的导弹数量减少,而飞行中的导弹数量增加。

现在,您可能对这些问题有了一个很好的答案,但您应该先完全映射您的模型,然后再开始使用它们可能不需要的身份来处理对象。

【讨论】:

  • “在不可变对象的世界中,两个具有相同值的对象是同一个对象”——但在我们想要建模的许多系统中,这不是真的。对象可能具有相同的属性,但与不同的对象相关。就像,你可以养两只棕色的奶牛,但它们的主人是不同的。那些棕色的奶牛不是同一个对象,不能这样对待。因此需要某种方式来唯一标识相关对象。
  • 太棒了!你有所有者的名字,所以把它们放在地图中,以所有者为键。我不是在开玩笑;如果在您的模型中,区分奶牛的唯一方法是通过它们的所有者,那么这就是您所需要的。 首先确定需要区分这些奶牛的各种环境,然后选择合适的解决方案。不要从损坏的模型开始。
  • 谢谢!假设奶牛的主人可以更改他们的名字。在这种情况下,您是否仍然建议不要使用任意数字标识符来链接对象?我想如果您仍然将其键入名称,那么每当所有者的名称更改时,您都必须构建一个新地图。
  • 你的模拟真的关心一头牛是否是同一头牛吗?如果每头奶牛都有一个主人,每个主人都喂他或她自己的奶牛,如果所有权变更意味着一个主人现在有两只奶牛,另一个现在有一只,这有关系吗?如果您确实需要跟踪每头奶牛的生活史,那么可能是的,但是然后给奶牛唯一的标识符。但是,您的模拟中的大多数对象都不需要它。如果给奶牛喂一捆捆干草,你会坚持认为每捆干草都是不同的物体吗?
  • @Jarrett 或者您可以在您的奶牛类型中添加一个“ID”字段,并为每头奶牛生成唯一的 ID。即使在 OOP 中,如果您有一个带有名称、颜色和重量属性的奶牛类,您将无法用唯一的 ID 区分两只相同的奶牛。这不是 OOP 或 FP 中的问题,只是编程中的问题。
【解决方案2】:

我的方法是将所有状态信息存储在数据记录中,例如

data ObjState = ObjState
    { objStName :: String
    , objStPos :: (Int, Int)
    , objStSize :: (Int, Int)
    } deriving (Eq, Show)

data Obj = Obj
    { objId :: Int
    , objState :: ObjState
    } deriving (Show)

instance Eq Obj where
    obj1 == obj2 = objId obj1 == objId obj2

并且状态应该由 API/库/应用程序管理。如果您需要指向可变结构的真正指针,则可以使用内置库,但除非您知道自己在做什么,否则使用它们被认为是不安全且危险的(即使那样,您也必须小心)。查看base 中的Foreign 模块了解更多信息。

【讨论】:

    【解决方案3】:

    在 Haskell 中,值和身份的概念是分离的。所有变量都是对值的不可变绑定。

    有几种类型的值是对另一个值的可变引用,例如IORefMVarTVar,这些可以用作标识。

    您可以通过比较两个MVars 来执行身份检查,并通过比较它们的引用值来执行相等性检查。

    Rich Hickey 的精彩演讲详细讨论了这个问题:http://www.infoq.com/presentations/Value-Values

    【讨论】:

      【解决方案4】:

      你总是可以写:

      > let mylist = take 25 $ cycle "a"
      > mylist
      "aaaaaaaaaaaaaaaaaaaaaaaaa"
      > zip mylist [1..]
      [('a',1),('a',2),('a',3),('a',4),('a',5),('a',6),('a',7),('a',8),('a',9),('a',10),
       ('a',11),('a',12),('a',13),('a',14),('a',15),('a',16),('a',17),('a',18),('a',19),
       ('a',20),('a',21),('a',22),('a',23),('a',24),('a',25)]
      

      如果我们不是在开玩笑 - 将其保存为数据的一部分

      data MyObj = MyObj {id ::Int, ...}
      

      更新

      如果我们想分别使用颜色和ids,我们可以在 Haskell 中进行下一步:

      data Color = Green | Red | Blue deriving (Eq, Show)
      
      data Car = Car {carid :: Int, color :: Color} deriving (Show)
      
      garage = [Car 1 Green, Car 2 Green, Car 3 Red]
      
      redCars = filter ((== Red) . color) garage
      
      greenCars = filter ((== Green) . color) garage
      
      paint2Blue car = car {color=Blue}
      
      isGreen = (== Green) . color
      
      newGarage = map (\car -> if isGreen car then paint2Blue car else car) garage
      

      并在 gchi 中查看结果:

      > garage
      [Car {carid = 1, color = Green},Car {carid = 2, color = Green},Car {carid = 3, color = Red}]
      > redCars
      [Car {carid = 3, color = Red}]
      > greenCars
      [Car {carid = 1, color = Green},Car {carid = 2, color = Green}]
      > newGarage
      [Car {carid = 1, color = Blue},Car {carid = 2, color = Blue},Car {carid = 3, color = Red}]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-11-20
        • 2022-09-29
        • 2012-02-26
        • 1970-01-01
        • 2017-04-07
        • 2017-04-10
        • 1970-01-01
        相关资源
        最近更新 更多