在Haskell的类型类中记录选择器 [英] Record selectors in Haskell's Type Classes
问题描述
我想用很少的默认方法实现 Type Class
,但是遇到一个错误,我不能在 type classes中使用
定义. record选择器
I want to implement a Type Class
with few default methods, but I'm getting an error, that I cannot use record selectors
inside type classes
definitions.
以下代码基本上创建了 type类
,该类定义了 add
函数,该函数应将元素添加到某些的
.这是代码: repr
记录中数据类型
The following code basically creates type class
which defines add
function, which should add an element to the repr
record of some data type
. Here is the code:
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中声明此类方法?
Is it possible to declare such methods in Haskell?
说明
我需要这样的设计,因为我有一些数据类型
,它们的行为类似.可以说,我们得到了 A
, B
和 C
数据类型
.他们每个人都应有一条记录 repr :: DG.Gr ab
,其中 a
和 b
对于每个 A
, B
和 C
.
I need such design because I've got some data types
, which behave in simmilar way. Lets say, we got A
, B
and C
data types
. Each of them should have a record repr :: DG.Gr a b
, where a
and b
are distinct for each of A
, B
and C
.
A
, B
和 C
具有相同的功能,例如 add
或 delete
(基本上是添加或删除元素来记录 repr
).如果这些数据类型共享许多功能,则有必要在 type class
中实现这些功能并创建该 type class
的实例-这些功能将为我们每个人实现数据类型
自动.
A
, B
and C
share the same functions, like add
or delete
(which basically add or delete elements to record repr
). If these data types share a lot of functions it make sense to implement the functions in type class
and make instances of this type class
- these functions will be implemented for each of our data type
automatically.
另外,当我调用 add
函数时,我会喜欢其中一些数据类型
(让我说 B
)的行为略有不同.当为 B
创建 type类
的 instance
时,很容易实现此行为.
Additional I would love some of these data types
(lets say I want B
) to behave slighty different when calling add
function on it. It is easy to implement this behaviour when making instance
of the type class
for B
.
推荐答案
-
记录更新语法
The record update syntax
<record-instance> { <record-field-name> = ..., ... }
当< record-instance>
是已知代数数据类型的实例/术语时,
起作用(因此,< record-field-name>
是已知字段),在您的代码中只是一些(特殊的)多态参数 gr
,因此您需要先将 gr
转换为 Gr
,然后对其进行更新,然后...
works when <record-instance>
is an instance/term of a known algebraic data type (so that <record-field-name>
is it known field), in your code it is just some (ad-hoc) polymorphic parameter gr
, so you need first to convert gr
to Gr
, then update it, and then...
我认为 gr
和 Gr
在某种意义上应该是等效的,也就是说,我们需要一个用于 repr
的逆函数. iface
,以便能够实现 add
.
I think that gr
and Gr
should be equivalent in some sense, i.e. we need an inverse function for repr
, say iface
, to be able to implement 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)]}"
如果某些数据类型为 A
和 B
aggregate Gr ab
(这样我们就无法编写与 repr
相反),那么我们可以执行以下操作:
If some data types A
and B
aggregate Gr a b
(so that we can't write an inverse for repr
), then we can do something like this:
{-# 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}
也可以在此处使用镜头:
{-# 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}
这篇关于在Haskell的类型类中记录选择器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!