【问题标题】:Current state of record types and subtyping in HaskellHaskell 中记录类型和子类型的当前状态
【发布时间】:2017-05-30 21:18:45
【问题描述】:

Haskell 中记录类型和子类型的当前状态是什么?

我知道已经完成了诸如重载记录名称等方面的工作。具体来说,我想创建三种不同的记录类型ABC 其中BC包含与A 相同的所有字段标签,但彼此之间不共享字段标签。然后,我希望能够编写f : A -> intg: B -> inth: C -> int 的函数,其中函数f 也接受BC 类型的参数。基本上,我希望BC 成为A 的子类型。更具体地说,如果我不必重复所有字段标签,那就太好了。在伪代码中,这类似于

data A = A { a :: String }
data B = B { A, b :: Char }
data C = C { C, c :: Float }

f :: A/B/C -> int
g :: B -> int
h :: C -> int

【问题讨论】:

  • Haskell 没有子类型,至少在 OOP 意义上没有。相反,你可以做data A = A { a :: String}; data B = B { bA :: A, b :: Char }; data C = C { cA :: A, c :: Float }.

标签: haskell


【解决方案1】:

有几种不同的方法可以实现这一点,但也有一些缺点。

以下是我正在使用的类型:

data A = A { a :: String}
data B = B { bA :: A, b :: Char}
data C = C { cA :: A, c :: Float}

Ad-hoc 多态性:所有这些类型都可以实现

您可以将f 定义为ABC 都是以下实例的CanF 类的方法:

class CanF a where
  f :: a -> Int

instance CanF A where
  f = length . a

instance CanF B where
  f = f . bA

instance CanF C where
  f = f . cA

根据A 的实例定义BC 的实例清楚地表明f 在每种情况下都做同样的事情。根据定义F 实例的类型,很容易使f 做不同的事情。这种方法的缺点是任何其他类似 f 的函数都需要添加为同一个“CanSomething”类的方法。

main :: IO ()
main = do
    print (f a)
    print (f b)
    print (f c)
  where
    a = A "Hello"
    b = B a 'H'
    c = C a 3.14

Ad-hoc 多态性:所有这些类型都可以表示为一个 A

另一种方法是将f 编写为一个受类约束的函数,该类始终为您提供A

class RepA a where
  getA :: a -> A

instance RepA A where
  getA = id

instance RepA B where
  getA = bA

instance RepA C where
  getA = cA

f :: RepA a => a -> Int
f = length . a . getA

在这里,您定义f 可以做什么的灵活性较小,这可能是好是坏。优点是您可以定义在 A 上工作的其他函数,而无需向您的类添加新方法。

函数记录

我首选的处理方法是函数记录方法。定义一个参数化数据类型,其中包含您要调用的函数。然后为您的记录类型定义专门的构造函数。这种方法的缺点是它通常更冗长。优点是您可以通过向f 函数提供不同的F 来交换行为。另一个优势是无需语言扩展即可完成更多工作。

data F a = F { f :: a -> Int }

af :: F A
af = F $ length . a

bf :: F B
bf = F $ f af . bA

cf :: F C
cf = F $ f af . cA

main :: IO ()
main = do
    print (f af a)
    print (f bf b)
    print (f cf c)
  where
    a = A "Hello"
    b = B a 'H'
    c = C a 3.14

【讨论】:

    【解决方案2】:

    没有。有人提议添加重载记录字段和一个有效的实现,但 AFAIK 尚未合并到 GHC 的头部。您可以阅读提案here。一旦它落地,我们将拥有类似于 rho 多态性但自动生成/推断的类型类的东西。注意这个不是子类型。 {a :: Int, b :: Bool} <: {a :: Int} 在 Haskell 中不是一个概念,相反我们可以这样说

     foo :: r {a :: Int} -> Int
     foo = a
    

    这真的会更像

     foo :: Has "a" Int r => r -> Int
     foo = a
    

    如果我们写了类似的东西

     foo :: {a :: Int} -> {a :: Int}
     foo = id
    

    并且希望它表现得像我们有子类型,我们可以做类似的事情

     foo _ = A {a = 1}
    

    并返回任何属于{a :: Int} 子类型的类型。

    图书馆有替代品,比如 Vinyl,在某种程度上,还有 lens。我建议现在调查这些,特别是如果您希望与 7.6/7.8 兼容。

    【讨论】:

      【解决方案3】:

      由于 HasField 类在 GHC 的 head 分支中,使用 GHC 的开发版本 8.2.0.20170310,我们得到了一个关于具有特定字段的记录的多态性的工作示例,它可以与手写结构子类型一起使用:

      {-# LANGUAGE DuplicateRecordFields, DataKinds, FlexibleContexts, TypeApplications #-}
      
      import GHC.Records (HasField(getField))
      
      data A = A { a :: String }
      data B = B { a :: String, b :: Char }
      data C = C { a :: String, c :: Float }
      
      -- | length of field "a"
      
      f :: HasField "a" rec String => rec -> Int
      f = length . getField @"a"
      
      main = do
          print $ f $ A "a"
          print $ f $ B "b" 'b'
          print $ f $ C "c" 1.5
      

      记录在“GHC 8.2 分支”用户指南 (https://github.com/ghc/ghc/blob/ghc-8.2/docs/users_guide/glasgow_exts.rst) 中搜索“记录字段选择器多态性”

      GHC 的 8.2.1 开发二进制文件来自 Herbert Riedel 的 Ubuntu PPA (https://launchpad.net/~hvr/+archive/ubuntu/ghc/+index?batch=150)

      【讨论】:

        猜你喜欢
        • 2010-12-26
        • 1970-01-01
        • 1970-01-01
        • 2013-07-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多