在Haskell中对结构相似类型的值进行操作

时间:2009-10-20 09:05:46

标签: haskell functional-programming types typeclass

对不起我非常有限的Haskell-fu。

我有一系列data类型,在不同的模块中定义,结构相同:

-- in module Foo
data Foo = Foo [Param]

-- in module Bar
data Bar = Bar [Param]

-- * many more elsewhere

我希望有一组在params列表上运行的函数,例如在列表中添加和删除元素(返回一个新的FooBar,其中包含不同的列表params,视情况而定)。

据我所知,即使我创建一个类型类并为每种类型创建实例,我也需要每次都定义所有这些函数,即:

-- in some imported module

class Parameterized a where
    addParam :: a -> Param -> a
    -- ... other functions

-- in module Foo

instance Parameterization Foo where
    addParam (Foo params) param = Foo (param:params)
    -- ... other functions

-- in module Bar

instance Parameterization Bar where
    -- this looks familiar...
    addParam (Bar params) param = Bar (param:params)
    -- ... other functions

这感觉很乏味 - 远远超过了我开始认为我做错了什么的程度。如果无法使用构造函数(?)进行模式匹配来提取值,那么如何减少这样的样板?

要反驳一个可能的参数:是的,我知道我可以简单地拥有一组函数(addParam等),它们会明确地列出每个构造函数和模式匹配的参数 - 但是就像我一样我相当模块化地构建这个模块(FooBar模块是非常独立的),我正在构建一个系统,其中将存在数十种类型,类型构造函数的详细集中列表似乎......错了。

很可能(很可能?)我的方法只是有缺陷,而且无论如何都不能以正确的方式构建类型层次结构 - 但因为我不能有一个data类型在某个地方并为每个模块中的类型添加一个新的构造函数(?)我很难理解如何获得一个漂亮的“插件”感觉,而不必每次都重新定义简单的实用程序函数。任何和所有善意的建议都很乐意接受。

3 个答案:

答案 0 :(得分:4)

您可以在类型类中使用函数的默认实现,例如

class Parameterized a where
    params :: a -> [Param]
    fromParams :: [Param] -> a
    addParam :: a -> Param -> a
    addParam x par = fromParams $ par : params x
    -- ... other functions

instance Parameterized Foo where
    params (Foo pars) = pars
    fromParams = Foo

但是,您的设计确实看起来很可疑。

答案 1 :(得分:3)

您可以为Foo和Bar使用newtype,然后使用-XGenerializedNewtypeDeriving自动基于底层结构(在本例中为列表)派生实现。

如果他们真的应该在结构上相似,但你已经以不能共享代码的方式对其进行编码,那么这就是一种可疑模式。

答案 2 :(得分:2)

从你的例子中很难说出正确的方法。

如果结构相似,为什么同时需要FooBar?你不能跟Foo一起去吗?

如果由于某些原因我看不到,你需要FooBar,你必须使用类型类,但你可以使用Template Haskell清理代码。

这样的东西
$(superDuper "Foo")

可以生成代码

data Foo = Foo [Param]

instance Parameterization Foo where
    addParam (Foo params) param = Foo (param:params)

,其中

superDuper :: String -> Q [Dec]
superDuper n =
  let name = mkName n
      dataD = DataD [] name [] [NormalC name [] ] -- correct constructor here
      instD = InstanceD [] ... -- add code here
  return [dataD, instD]

那至少可以摆脱锅炉板编码。