データ型ジェネリックプログラミング技法を使用して、レコードのすべてのフィールドをある種の「一様」な仕方で変換することができます。
おそらく、レコードのすべてのフィールドに、使用するtypeclassが実装されています(典型的な例はShow
です)。あるいは、関数を含む "類似の"形の別のレコードがあり、元のレコードの対応するフィールドに各関数を適用する必要があります。
このような用途では、generics-sopライブラリが適しています。それは、sequence
またはap
のような関数のアナログを提供するが、レコードのすべてのフィールドに対して機能する余分なタイプレベルの機械で、GHCのデフォルトのジェネリックス機能を拡張します。
generics-sopを使用して、少し冗長なバージョンのmerge
機能を作成しようとしました。いくつかの予備の輸入:ジェネリック-SOPの機能によって使用可能な形式にバイナリ操作を持ち上げる
{-# language TypeOperators #-}
{-# language DeriveGeneriC#-}
{-# language TypeFamilies #-}
{-# language DataKinds #-}
import Control.Applicative (liftA2)
import qualified GHC.Generics as GHC
import Generics.SOP
helper機能:オペレータと作品のベクトルをとり
fn_2' :: (a -> a -> a) -> (I -.-> (I -.-> I)) a -- I is simply an Identity functor
fn_2' = fn_2 . liftA2
一般的なマージ関数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
タイプレベルの李を返すタイプのファミリーですデータ型の構造を記述するリスト。外側のリストはコンストラクタ用であり、内側のリストには各コンストラクタのフィールドの型が含まれています。
Code a ~ '[ xs ]
の制約の一部には、外側のリストにちょうど1つの要素を持たせることによって、 "データ型には1つのコンストラクタしか持てません"と記載されています。
(SOP (Z _)
パターンマッチは、レコードのジェネリック表現からフィールド値の(heterogeneus)ベクトルを抽出します。 SOP
は "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
と:*
コンストラクタはオペレータのベクトル(型がn進生成物から、NP
呼ばれる)を構築するために使用されます。ベクトルがレコードのフィールド数と一致しない場合、プログラムはコンパイルされません。 はあなたのレコードの種類は非常に均一であることを考えると、操作のベクトルを作成する別の方法は、各フィールドタイプに補助型クラスのインスタンスを定義し、hcpure
機能を使用することである。
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
を組み合わせた)関数を使用すると、事をさらに単純化することができます。
レコードフィールドに名前を付ける必要がありますか( 'Foo'はレコード以外のものになる可能性があります)? – Alec
このXYの臭いが問題です。たぶん、そのような「Foo」を全く持たないという解決策があるかもしれませんが、その判断をするには、「Foo」が解決しなければならない問題についての情報を与えなければなりません。 – Bakuriu
おそらく "generics-sop"のようなジェネリックライブラリを使って、データ型ジェネリックプログラミングのユースケースのようです。 – danidiaz