什么是管理大型Haskell记录的更好方式? [英] What's a better way of managing large Haskell records?

查看:113
本文介绍了什么是管理大型Haskell记录的更好方式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

用字母替换字段名称,我有这样的情况:

Replacing fields names with letters, I have cases like this:

data Foo = Foo { a :: Maybe ...
               , b :: [...]
               , c :: Maybe ...
               , ... for a lot more fields ...
               } deriving (Show, Eq, Ord)

instance Writer Foo where
  write x = maybeWrite a ++
            listWrite  b ++
            maybeWrite c ++
            ... for a lot more fields ...

parser = permute (Foo
                   <$?> (Nothing, Just `liftM` aParser)
                   <|?> ([], bParser)
                   <|?> (Nothing, Just `liftM` cParser)
                   ... for a lot more fields ...

-- this is particularly hideous
foldl1 merge [foo1, foo2, ...]
merge (Foo a b c ...seriously a lot more...)
      (Foo a' b' c' ...) = 
        Foo (max a a') (b ++ b') (max c c') ...

什么技术可以让我更好地管理这种增长?

What techniques would allow me to better manage this growth?

在完美的世界 a b c 都将是同一类型,所以我可以将它们保存在列表中,但是它们可以是许多不同的类型。我非常感兴趣的方式来折叠记录,而不需要大量的模式。

In a perfect world a, b, and c would all be the same type so I could keep them in a list, but they can be many different types. I'm particularly interested in any way to fold the records without needing the massive patterns.

我正在使用这个大记录来保存由排列解析vCard格式

I'm using this large record to hold the different types resulting from permutation parsing the vCard format.

更新

我已经实现了仿制药 foldl 的方法。他们都工作,他们都减少三个大字段列表到一个。

I've implemented both the generics and the foldl approaches suggested below. They both work, and they both reduce three large field lists to one.

推荐答案

数据类型 - 通用编程技术可用于以某种统一的方式转换记录的所有字段。

Datatype-generic programming techniques can be used to transform all the fields of a record in some "uniform" sort of way.

也许记录中的所有字段都会实现我们要使用的类型类(典型的例子是显示) 。或者也许我们有另外一个包含函数的类似形状的记录,我们要将每个函数应用到原始记录的相应字段。

Perhaps all the fields in the record implement some typeclass that we want to use (the typical example is Show). Or perhaps we have another record of "similar" shape that contains functions, and we want to apply each function to the corresponding field of the original record.

对于这些类型使用, generic-sop 库是一个不错的选择。它扩展了GHC的默认通用功能,具有额外的类型级机制,提供类似于 sequence ap ,一个记录的字段。

For these kinds of uses, the generics-sop library is a good option. It expands the default Generics functionality of GHC with extra type-level machinery that provides analogues of functions like sequence or ap, but which work over all the fields of a record.

使用泛型sop,我试图创建一个稍微不太详细的合并版本 funtion。一些初步导入:

Using generics-sop, I tried to create a slightly less verbose version of your merge funtion. Some preliminary imports:

{-# language TypeOperators #-}
{-# language DeriveGeneric #-}
{-# language TypeFamilies #-}
{-# language DataKinds #-}

import Control.Applicative (liftA2)
import qualified GHC.Generics as GHC
import Generics.SOP

A 帮助器函数,将二进制操作提升为泛型函数可用的形式 - sop:

A helper function that lifts a binary operation to a form useable by the functions of generics-sop:

fn_2' :: (a -> a -> a) -> (I -.-> (I -.-> I)) a -- I is simply an Identity functor
fn_2' = fn_2 . liftA2

一个通用合并函数,它采用运算符的向量,并对任何导出的单构造函数记录工作 Generic

A general merge function that takes a vector of operators and works on any single-constructor record that derives Generic:

merge :: (Generic a, Code a ~ '[ xs ]) => NP (I -.-> (I -.-> I)) xs -> a -> a -> a 
merge funcs reg1 reg2 =
    case (from reg1, from reg2) of 
        (SOP (Z np1), SOP (Z np2)) -> 
            let npResult  = funcs `hap` np1 `hap` np2
            in  to (SOP (Z npResult))

代码 是一个类型系列,它返回描述数据类型结构的列表的类型级列表。外部列表用于构造函数,内部列表包含每个构造函数的字段类型。

Code is a type family that returns a type-level list of lists describing the structure of a datatype. The outer list is for constructors, the inner lists contain the types of the fields for each constructor.

代码a〜'[xs] / code>约束的一部分表示数据类型只能有一个构造函数,要求外部列表只有一个元素。

The Code a ~ '[ xs ] part of the constraint says "the datatype can only have one constructor" by requiring the outer list to have exactly one element.

(SOP(Z _)模式匹配从记录的泛型中提取字段值的(异质)矢量代表性。 SOP 代表产品总和。

The (SOP (Z _) pattern matches extract the (heterogeneus) vector of field values from the record's generic representation. SOP stands for "sum-of-products".

具体示例:

data Person = Person
    {
        name :: String
    ,   age :: Int
    } deriving (Show,GHC.Generic)

instance Generic Person -- this Generic is from generics-sop

mergePerson :: Person -> Person -> Person
mergePerson = merge (fn_2' (++) :* fn_2' (+) :* Nil)

Nil :* 构造函数用于构建运算符的向量(类型称为 NP ,来自n-ary产品)。如果向量与记录中的字段数不匹配,程序将不会编译。

The Nil and :* constructors are used to build the vector of operators (the type is called NP, from n-ary product). If the vector doesn't match the number of fields in the record, the program won't compile.

更新。在您的记录中高度统一,创建操作向量的另一种方法是定义每个字段类型的辅助类型类的实例,然后使用 hcpure 功能:

Update. Given that the types in your record are highly uniform, an alternative way of creating the vector of operations is to define instances of an auxiliary typeclass for each field type, and then use the hcpure function:

class Mergeable a where
    mergeFunc :: a -> a -> a

instance Mergeable String where
    mergeFunc = (++)

instance Mergeable Int where
    mergeFunc = (+)

mergePerson :: Person -> Person -> Person
mergePerson = merge (hcpure (Proxy :: Proxy Mergeable) (fn_2' mergeFunc))

hcliftA2 函数(组合 hcpure fn_2 hap )可用于进一步简化。

The hcliftA2 function (that combines hcpure, fn_2 and hap) could be used to simplify things further.

这篇关于什么是管理大型Haskell记录的更好方式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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