在Haskell的类型类中记录选择器 [英] Record selectors in Haskell's Type Classes

查看:40
本文介绍了在Haskell的类型类中记录选择器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想用很少的默认方法实现 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.

推荐答案

  1. 记录更新语法

  1. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆