Haskell中记录类型和子类型的当前状态

6

目前Haskell中记录类型和子类型的现状如何? 我知道已经有了一些关于重载记录名称等方面的工作。 具体来说,我想创建三种不同的记录类型ABC,其中BC包含与A相同的所有字段标签,但彼此之间不共享字段标签。 然后,我想编写函数,其中f:A -> intg:B -> inth:C -> int,其中函数f还接受具有类型BC的参数。 基本上,我希望BCA的子类型。 更具体地说,如果我不必重复所有字段标签,那将是很好的。 伪代码如下:

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没有子类型,至少不是面向对象编程中的那种。相反,你可以这样做:data A = A { a :: String}; data B = B { bA :: A, b :: Char }; data C = C { cA :: A, c :: Float } - bheklilr
3个回答

8

目前还没有这样的功能。有一个提案,即添加重载记录字段和工作实现,但据我所知,它尚未合并到GHC的主干中。您可以在此处阅读有关该提案的内容。一旦该功能上线,我们将拥有类似于rho多态性的东西,但是自动生成/推断类型类。请注意,这不是子类型化。在Haskell中,{a :: Int, b :: Bool} <: {a :: Int}不是一个概念,而是我们将能够说出类似以下的话:

 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兼容,请现在先调查这些。


8

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

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

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

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

您可以将f定义为CanF类的一个方法,该类是ABC的所有实例的公共类:

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

即时多态:所有这些类型都可以表示为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,而不必向类中添加新方法。

函数记录

我喜欢使用函数记录方法来处理这个问题。定义一个参数化数据类型,其中包含要调用的函数。然后为你的记录类型定义专门的构造函数。这种方法的缺点是通常更冗长。优点是,通过提供不同的Ff函数,你可以交换行为。另一个优点是,在不需要语言扩展的情况下,你可以完成更多工作。

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

4

由于HasField类在GHC的主干分支中,使用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分支”用户指南中记录了“Record field selector polymorphism”(https://github.com/ghc/ghc/blob/ghc-8.2/docs/users_guide/glasgow_exts.rst)。可以搜索该内容。
从Herbert Riedel的Ubuntu PPA(https://launchpad.net/~hvr/+archive/ubuntu/ghc/+index?batch=150)获取GHC 8.2.1开发二进制文件。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接