【问题标题】:Record selectors in Haskell's Type ClassesHaskell 类型类中的记录选择器
【发布时间】:2013-07-19 18:19:02
【问题描述】:

我想用几个默认方法实现Type Class,但我遇到了一个错误,我不能在type classes 定义中使用record selectors

下面的代码基本上创建了type class,它定义了add函数,它应该在一些data typerepr记录中添加一个元素。代码如下:

import qualified Data.Graph.Inductive     as DG

class Graph gr a b where
    empty :: DG.Gr a b
    empty = DG.empty

    repr :: gr -> DG.Gr a b

    -- following function declaration does NOT work:
    add :: a -> gr -> gr
    add el g = g{repr = DG.insNode el $ repr g}

编译器报错:

repr is not a record selector
In the expression: g {repr = DG.insNode el $ repr g}
In an equation for add:
    add el g = g {repr = DG.insNode el $ repr g}

是否可以在 Haskell 中声明这样的方法?

澄清

我需要这样的设计,因为我有一些data types,它们的行为方式类似。可以说,我们得到了 ABC data types。他们每个人都应该有一个记录repr :: DG.Gr a b,其中ab 对于ABC 中的每一个都是不同的。

ABC 共享相同的功能,例如adddelete(基本上添加或删除元素以记录repr)。如果这些数据类型共享很多函数,那么实现 type class 中的函数并创建此 type class 的实例是有意义的 - 这些函数将为我们的每个 data type 自动实现。

另外,我希望其中一些data types(假设我想要B)在调用add 函数时表现得略有不同。在将type class 中的instance 制作为B 时,很容易实现此行为。

【问题讨论】:

  • 答案是“不”,但“有点,使用镜头”,但更重要的是,我觉得这里对类的用途存在根本性的误解。如果您说为什么要开设这样的课程,那将很有帮助;我们也许可以提出一个更惯用的替代方案。
  • @DanielWagner - 我已经为我要解决的问题添加了说明 - 我希望现在已经足够清楚了,我为什么要这样做:)
  • 查看我的更新答案。在第二个示例中,我使用update 方法执行实际更新(可以在实例中使用该记录更新语法来实现),第三个示例使用Control.Lens
  • @JJJ 我认为镜头是我要找的!谢谢!

标签: haskell types record typeclass


【解决方案1】:
  1. 记录更新语法

     <record-instance> { <record-field-name> = ..., ... }
    

    &lt;record-instance&gt;已知 代数数据类型的实例/术语时工作(因此&lt;record-field-name&gt; 是已知字段),在您的代码中它只是一些(临时)多态参数gr,所以需要先将gr转换为Gr,然后更新,然后...

  2. 我认为grGr在某种意义上应该是等价的,即我们需要repr的反函数,比如iface,以便能够实现add

这是一个例子:

{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-}

data Gr a b = Gr { _internal :: [(a, b)] } deriving ( Show, Read )

class Graph gr a b where

  repr :: gr -> Gr a b
  iface :: Gr a b -> gr

  -- iface . repr == id {gr}
  -- repr . iface == id {Gr a b}

  -- add element via "interface" (get a representation via @repr@, update it, and then 
  -- return an interface back with @iface@)
  add :: (a, b) -> gr -> gr
  add el g = let r = repr g in iface r { _internal = el : _internal r }
  -- or
  add el = iface . insNode el . repr where
    insNode x (Gr xs) = Gr (x : xs) -- or whatever

instance Graph String Int Int where
  repr = read
  iface = show

test :: String
test = add (1 :: Int, 2 :: Int) "Gr { _internal = [] }"
-- test => "Gr {_internal = [(1,2)]}"

如果某些数据类型AB 聚合 Gr a b(这样我们就不能为repr 写逆),那么我们可以这样做:

{-# LANGUAGE MultiParamTypeClasses #-}

data Gr a b = Gr [(a, b)] deriving ( Show )

class Graph gr a b where

  repr :: gr -> Gr a b

  update :: gr -> (Gr a b -> Gr a b) -> gr
  -- 2: update :: gr -> Gr a b -> gr

  add :: (a, b) -> gr -> gr
  add el g = update g $ insNode el
    -- 2: update g (insNode el $ repr g)
    where insNode x (Gr xs) = Gr (x : xs)

data A = A { _aRepr :: Gr Char Char, _aRest :: Char } deriving ( Show )
data B = B { _bRepr :: Gr Int Int, _bRest :: Int } deriving ( Show )

instance Graph A Char Char where
  repr = _aRepr
  update r f = r { _aRepr = f $ _aRepr r }
  -- 2: update r g = r { _aRepr = g }

instance Graph B Int Int where
  repr = _bRepr
  update r f = r { _bRepr = f $ _bRepr r }
  -- 2: update r g = r { _bRepr = g }

testA :: A
testA = add ('1', '2') $ A (Gr []) '0'
-- => A {_aRepr = Gr [('1','2')], _aRest = '0'}

testB :: B
testB = add (1 :: Int, 2 :: Int) $ B (Gr []) 0
-- => B {_bRepr = Gr [(1,2)], _bRest = 0}

这里也可以使用lenses

{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}

import Control.Lens

data Gr a b = Gr [(a, b)] deriving ( Show )

insNode :: (a, b) -> Gr a b -> Gr a b
insNode x (Gr xs) = Gr (x : xs)

class Graph gr a b where
  reprLens :: Simple Lens gr (Gr a b)

add :: Graph gr a b => (a, b) -> gr -> gr
add el = reprLens %~ insNode el

data A = A { _aRepr :: Gr Char Char, _aRest :: Char } deriving ( Show )
data B = B { _bRepr :: Gr Int Int, _bRest :: Int } deriving ( Show )

makeLenses ''A
makeLenses ''B

instance Graph A Char Char where
  reprLens = aRepr

instance Graph B Int Int where
  reprLens = bRepr

main :: IO ()
main = do
  let a = A (Gr []) '0'
      b = B (Gr []) 0
  print $ add ('0', '1') a
  print $ add (0 :: Int, 1 :: Int) b
-- A {_aRepr = Gr [('0','1')], _aRest = '0'}
-- B {_bRepr = Gr [(0,1)], _bRest = 0}

【讨论】:

    【解决方案2】:

    您可以尝试这样的事情(使用元组列表作为示例,而不是 DG

    {-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, TypeSynonymInstances #-}
    
    class MyClass g a b | g -> a b where
          extract :: g -> [(a,b)]
          construct :: [(a,b)] -> g 
    
          empty :: g
          empty = construct []
    
          add :: (a,b) -> g  -> g
          add i d = construct $  [i] ++ (extract d) 
    
    data A  = A {reprA :: [(Int,Int)]}
    
    instance MyClass A Int Int  where
             extract = reprA
             construct = A
    
    data B  = B {reprB :: [(String,String)]}
    
    instance MyClass B String String  where
             extract = reprB
             construct = B
    

    【讨论】:

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