代码之家  ›  专栏  ›  技术社区  ›  Clint Miller

haskell记录语法和类型类

  •  13
  • Clint Miller  · 技术社区  · 15 年前

    假设我有两种数据类型foo和bar。foo有字段x和y。bar有字段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进行转换,以获得反映命令行参数的最终记录。对于一个程序来说,这种方法非常有效,但是我正在尝试在所有程序中重新使用代码。

    4 回复  |  直到 11 年前
        1
  •  5
  •   Dan    15 年前

    你想要 extensible records 据我所知,这是哈斯克尔谈论最多的话题之一。目前在如何实施这一问题上似乎没有多少共识。

    在您的案例中,似乎可以使用类似于中实现的异类列表,而不是普通的记录。 HList .

    同样,这里似乎只有两个层次:公共和程序。因此,也许您应该为公共选项定义一个公共记录类型,为每个程序定义一个特定于程序的记录类型,并在这些类型的元组上使用statet。对于常见的内容,您可以添加组成 fst 使用公共访问器,因此调用方看不到它。

        2
  •  3
  •   Greg Bacon    15 年前

    您可以使用以下代码

    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' }
    

    您在代码中建模什么?如果我们对这个问题了解得更多,我们可以建议一些比这个面向对象的设计更简单的东西,这些东西被引入到一种功能性语言中。

        3
  •  2
  •   ADEpt    15 年前

    在我看来,这是一份仿制药的工作。如果您可以用不同的newtypes标记您的int,那么您就可以编写(使用uniplate、module 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 ]
    

    这将从OPT内的任何位置提取所有其他的。

    修改也是可能的。

        4
  •  1
  •   Jonathan Fischoff    15 年前

    如果将类型实例设置为可折叠,则会得到一个tolist函数,可以将该函数用作访问器的基础。

    如果Foldable对您没有任何帮助,那么正确的方法可能是将您想要的接口定义为一个类型类,并找到一种自动生成派生值的好方法。

    也许是从做中衍生出来的

    deriving(Data)
    

    你可以使用GMAP组合器来关闭你的访问。