【问题标题】:Why doesn't Haskell/GHC support record name overloading为什么 Haskell/GHC 不支持记录名重载
【发布时间】:2012-06-19 11:52:02
【问题描述】:

我是 Haskell 新手。我注意到 Haskell 不支持记录名称重载:

-- Records.hs

data Employee = Employee
  { firstName :: String
  , lastName :: String
  , ssn :: String
  } deriving (Show, Eq)

data Manager = Manager
  { firstName :: String
  , lastName :: String
  , ssn :: String
  , subordinates :: [Employee]
  } deriving (Show, Eq)

当我编译这个时,我得到:

[1 of 1] Compiling Main             ( Records.hs, Records.o )

Records.hs:10:5:
    Multiple declarations of `firstName'
    Declared at: Records.hs:4:5
                 Records.hs:10:5

Records.hs:11:5:
    Multiple declarations of `lastName'
    Declared at: Records.hs:5:5
                 Records.hs:11:5

Records.hs:12:5:
    Multiple declarations of `ssn'
    Declared at: Records.hs:6:5
                 Records.hs:12:5

鉴于 Haskell 类型系统的“强度”,编译器似乎应该很容易确定要访问哪个字段

emp = Employee "Joe" "Smith" "111-22-3333"
man = Manager "Mary" "Jones" "333-22-1111" [emp]
firstName man
firstName emp

是否有一些我没有看到的问题。我知道 Haskell 报告不允许这样做,但为什么不呢?

【问题讨论】:

  • 这根本不是您问题的答案,但是每当出现像您这样的情况时,我通常会将数据类型拆分为单独的模块。例如,我可能会创建一个Employee 模块和一个Manager 模块,并将它们分别导入为EM,然后使用E.firstNameM.firstName 等。这给了我相当不错的语法。 (我并不是说这一定是个好主意,但这是我最终所做的,而且在我的案例中效果很好)。
  • 是的,但这似乎是一种优雅的语言中的“kludge”。

标签: haskell record naming


【解决方案1】:

历史原因。 have beenmany competing designs 为 Haskell 提供了更好的记录系统——事实上,有很多 no consensus 可以到达。然而。

【讨论】:

    【解决方案2】:

    目前的记录系统不是很复杂。如果没有记录语法,它主要是一些语法糖,可以用于样板文件。

    特别是这个:

    data Employee = Employee
      { firstName :: String
      , lastName :: String
      , ssn :: String
      } deriving (Show, Eq)
    

    生成(除其他外)一个函数firstName :: Employee -> String

    如果你在同一个模块中也允许这种类型:

    data Manager = Manager
      { firstName :: String
      , lastName :: String
      , ssn :: String
      , subordinates :: [Employee]
      } deriving (Show, Eq)
    

    那么firstName 函数的类型是什么?

    它必须是重载同名的两个独立函数,这是 Haskell 不允许的。除非你想象这会隐式生成一个类型类并为所有具有名为 firstName 的字段创建实例(在一般情况下会变得混乱,当字段可能具有不同的类型时),否则 Haskell 的当前记录系统不会运行能够在同一个模块中支持多个同名字段。 Haskell 目前甚至没有尝试做任何此类事情。

    当然,它可以做得更好。但是有一些棘手的问题需要解决,基本上没有人想出解决方案来让每个人都相信有一个最有希望的方向。

    【讨论】:

    • 我猜你可以创建一个类型类,然后让类型类方法调用特定的记录版本,但这会给通常不需要它的语言添加很多样板。
    • 如果您允许字段重载,则firstName 函数应具有forall a. a 类型。使用类型推断或显式类型声明,此类型应该是专门的。 Agda 中的记录构造函数是这样工作的。
    【解决方案3】:

    避免这种情况的一种选择是将数据类型放在不同的模块中并使用合格的导入。通过这种方式,您可以在不同的数据记录上使用相同的字段访问器,并使您的代码保持简洁和可读性。

    您可以为员工创建一个模块,例如

    module Model.Employee where
    
    data Employee = Employee
      { firstName :: String
      , lastName :: String
      , ssn :: String
      } deriving (Show, Eq)
    

    还有一个用于管理器的模块,例如:

    module Model.Manager where
    
    import Model.Employee (Employee)
    
    data Manager = Manager
      { firstName :: String
      , lastName :: String
      , ssn :: String
      , subordinates :: [Employee]
      } deriving (Show, Eq)
    

    然后,无论您想在哪里使用这两种数据类型,您都可以将它们导入合格并按如下方式访问它们:

    import           Model.Employee (Employee)
    import qualified Model.Employee as Employee
    import           Model.Manager (Manager)
    import qualified Model.Manager as Manager
    
    emp = Employee "Joe" "Smith" "111-22-3333"
    man = Manager "Mary" "Jones" "333-22-1111" [emp]
    
    name1 = Manager.firstName man
    name2 = Employee.firstName emp
    

    请记住,毕竟您使用的是两种不同的数据类型,因此 Manger.firstName 是 Employee.firstName 之外的另一个函数,即使您知道这两种数据类型都代表一个人并且每个人都有一个名字。但是,抽象数据类型能走多远取决于您自己,例如从那些“属性集合”中创建一个 Person 数据类型。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-03
      • 1970-01-01
      • 2012-03-11
      • 2022-08-18
      • 1970-01-01
      • 2011-07-10
      • 2023-01-04
      相关资源
      最近更新 更多