假设我有两种数据类型Foo和Bar。 Foo有字段x和y。条形图有字段x和z。我希望能够编写一个函数,将Foo或Bar作为参数,提取x值,对其执行一些计算,然后返回一个新的Foo或Bar,并相应地设置x值。
这是一种方法:
class HasX a where
getX :: a -> Int
setX :: a -> Int -> a
data Foo = Foo Int Int deriving Show
instance HasX Foo where
getX (Foo x _) = x
setX (Foo _ y) val = Foo val y
getY (Foo _ z) = z
setY (Foo x _) val = Foo x val
data Bar = Bar Int Int deriving Show
instance HasX Bar where
getX (Bar x _) = x
setX (Bar _ z) val = Bar val z
getZ (Bar _ z) = z
setZ (Bar x _) val = Bar x val
modifyX :: (HasX a) => a -> a
modifyX hasX = setX hasX $ getX hasX + 5
问题在于所有这些getter和setter都很难写,特别是如果我将Foo和Bar替换为具有大量字段的真实数据类型。
Haskell的记录语法提供了一种更好的方法来定义这些记录。但是,如果我尝试定义像这样的记录
data Foo = Foo {x :: Int, y :: Int} deriving Show
data Bar = Foo {x :: Int, z :: Int} deriving Show
我会收到一条错误消息,说x被多次定义。并且,我没有看到任何方法来创建类型类的这些部分,以便我可以将它们传递给modifyX。
有解决这个问题的干净方法,还是我坚持定义自己的getter和setter?换句话说,有没有办法将记录语法创建的函数与类型类(getter和setter)连接起来?
修改
这是我正在努力解决的真正问题。我正在编写一系列相关程序,它们都使用System.Console.GetOpt来解析它们的命令行选项。这些程序中会有很多命令行选项,但有些程序可能有额外的选项。我希望每个程序能够定义包含其所有选项值的记录。然后我从一个默认记录值开始,然后通过StateT monad和GetOpt进行转换,以获得反映命令行参数的最终记录。对于单个程序,这种方法非常有效,但我正在尝试找到一种在所有程序中重用代码的方法。
答案 0 :(得分:5)
你想要extensible records,我收集的是Haskell中最受关注的主题之一。目前似乎没有就如何实施它达成共识。
在您的情况下,您似乎可以使用异构列表而不是普通记录,例如HList中实现的列表。
然后再次,你似乎在这里只有两个级别:普通和程序。因此,您可能只应为每个程序定义公共选项的公共记录类型和特定于程序的记录类型,并在这些类型的元组上使用StateT。对于常见的东西,您可以添加使用公共访问器组成fst
的别名,这样对于调用者来说它是不可见的。
答案 1 :(得分:3)
您可以使用
等代码data Foo = Foo { fooX :: Int, fooY :: Int } deriving (Show)
data Bar = Bar { barX :: Int, barZ :: Int } deriving (Show)
instance HasX Foo where
getX = fooX
setX r x' = r { fooX = x' }
instance HasX Bar where
getX = barX
setX r x' = r { barX = x' }
您在代码中建模的是什么?如果我们对这个问题有了更多的了解,那么我们可以提出一些不那么尴尬的东西,而不是这种面向对象的设计被用作函数式语言。
答案 2 :(得分:2)
对我而言似乎是一个泛型工作。如果你可以使用不同的newtype标记你的Int,那么你就可以编写(使用uniplate,模块PlateData):
data Foo = Foo Something Another deriving (Data,Typeable)
data Bar = Bar Another Thing deriving (Data, Typerable)
data Opts = F Foo | B Bar
newtype Something = S Int
newtype Another = A Int
newtype Thing = T Int
getAnothers opts = [ x | A x <- universeBi opts ]
这将从Opts内的任何地方提取所有的另一个。
也可以进行修改。
答案 3 :(得分:1)
如果您创建了Foldable的类型实例,则会获得一个toList函数,您可以将其用作访问者的基础。
如果Foldable不是你的任何东西,那么正确的方法是将你想要的接口定义为类型类,并找出一种自动生成派生值的好方法。
也许来自于做
deriving(Data)
您可以使用gmap组合器来关闭您的访问权限。