18

Haskellプラットフォームだけで多数の重複するモジュールを持つジェネリックライブラリがいくつかありますが(、、、、sybData.Typeable、非常Data.DataGHC.Generics基本的なジェネリックプログラミングタスクで問題が発生しています。

同じ形状のタイプ間で変換できるようにしたい、つまり、同型タイプ間で多形の型付き変換関数が必要です。これは、基本的に、このペーパーの最後(PDF)で提供されているインデックス付きタイプファミリーについて説明しています。

ボイラープレートを廃棄することではなく、合計と製品の抽象化を中心に新しいライブラリを構築できるようにすることに関心があります。

以下の質問は、GHC.Generic私が必要としているものに最も近いと思った点ですが、他の解決策も歓迎します。


次の2種類は同じ形です

data Pair = Pair Char Int deriving (Generic, Show)
data Pair2 = Pair2 Char Int deriving (Generic, Show)

GHC.Genericsを使用してそれらの間で値を変換したいと思います。以下は、すべてのファントムパラメータおよびその他のナンセンスのためにタイプチェックに失敗します。

f :: Pair -> Pair2
f = to . from

最終的には、任意の(または他のクラスがこれをサポートできる)インスタンスfromIntegerのポリモーフィックな戻り値を持つそれに類似した関数が必要です。Generic私は次のようなものを探していると思いGHC.Genericsます:

--class:
type family NormalForm a
class ToGeneric a where
    to :: a -> NormalForm a
class FromGeneric b where
    from :: NormalForm b -> b

--examples:
data A = A Char Int deriving Show
data B = B Char Int deriving Show

type instance NormalForm A = (Char,Int)
instance ToGeneric A where
    to (A a b) = (a,b)
instance FromGeneric A where
    from (a,b) = A a b

type instance NormalForm B = (Char,Int)
instance ToGeneric B where
    to (B a b) = (a,b)
instance FromGeneric B where
    from (a,b) = B a b

-- the function I'm looking for
coerce :: (ToGeneric a, FromGeneric b, NormalForm a ~ NormalForm b)=> a -> b
coerce = from . to

上記で私たちは私が望むすべてを行うことができます:

*Main> (coerce $A 'a' 1) :: B
B 'a' 1
*Main> (coerce $A 'a' 1) :: A
A 'a' 1

編集:fこれは、実際には、ネイサンハウエルの関数が以下でどのように機能するように見えるかです。

質問

  1. これは、現在haskellプラットフォームにあるライブラリで行うことができますか?

  2. そうでない場合は、 THに頼らずに、などの既存のderivingメカニズムを活用するライブラリを定義できますか?GenericData

4

2 に答える 2

9

それは可能であり、比較的痛みはありません。直接使用する場合とは異なりunsafeCoerce、タイプが揃っていない場合はビルドブレークが発生します。fおそらく、使用するのに十分なコンパイル時タイプの安全性を提供し、ファミリunsafeCoerceでの作業を回避するために、等式制約に依存することができます。Rep

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeFamilies #-}

import GHC.Generics

data Pair1 = Pair1 Char Int deriving (Generic, Show)
data Pair2 = Pair2 Char Int deriving (Generic, Show)

data Triple1 = Triple1 Char Int Double deriving (Generic, Show)
data Triple2 = Triple2 Char Int Double deriving (Generic, Show)

f :: (Generic a, Generic c, Rep a ~ D1 da (C1 ca f), Rep c ~ D1 db (C1 cb f))
  => a -> c
f = to . M1 . M1 . unM1 . unM1 . from
-- this might also be acceptable:
-- f = unsafeCoerce

p1 :: Pair1 -> Pair2
p1 = f

p2 :: Pair2 -> Pair1
p2 = f

t1 :: Triple1 -> Triple2
t1 = f

t2 :: Triple2 -> Triple1
t2 = f

それを実行すると、期待される結果が得られます。

*Main> p1 $ Pair1 'x' 1
Pair2 'x' 1
*Main> p2 $ Pair2 'x' 1
Pair1 'x' 1
*Main> t1 $ Triple1 'y' 2 3.0
Triple2 'y' 2 3.0
*Main> t2 $ Triple2 'y' 2 3.0
Triple1 'y' 2 3.0
于 2012-08-09T21:32:16.717 に答える
4

「同じ形状」とは、データ型がコンストラクター名、レコードセレクター、および型の同義語まで等しいことを意味する場合、データ型の変換は表現をトラバースするのと同じくらい簡単です。

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts, FlexibleInstances #-}

import GHC.Generics

conv
  :: (Generic a, Generic b, Conv (Rep a) (Rep b))
  => a -> b
conv = to . cv . from

class Conv a b where
  cv :: a x -> b x

-- skip irrelevant parts: datatype name, constructor name, selector
instance Conv f1 f2 => Conv (M1 i1 c1 f1) (M1 i2 c2 f2) where
  cv = M1 . cv . unM1

instance (Conv a1 a2, Conv b1 b2) => Conv (a1 :*: b1) (a2 :*: b2) where
  cv ~(a :*: b) = cv a :*: cv b

instance (Conv a1 a2, Conv b1 b2) => Conv (a1 :+: b1) (a2 :+: b2) where
  cv (L1 a) = L1 $ cv a
  cv (R1 b) = R1 $ cv b

-- copy values
instance Conv U1 U1 where cv = id
instance Conv (K1 R c) (K1 R c) where cv = id

テストケース:

data A = A1 String Int | A2 (Int,Int) deriving (Generic, Show)
data B = B1 [Char] Int | B2 { xy :: (Int,Int) } deriving (Generic, Show)
data X = X Int Int deriving (Generic, Show)

*Main> conv $ X 3 14 :: (Int,Int)
(3,14)
*Main> conv $ A1 "hello" 42 :: B
B1 "hello" 42
*Main> conv $ A2 (13,42) :: B
B2 {xy = (13,42)}

アップデート

さらにいくつかのインスタンスにより、より興味深い変換が可能になります。

instance Conv U1 (M1 S s (K1 R ())) where
  cv _ = M1 $ K1 ()
-- *> conv (Nothing :: Maybe Int) :: Either () Int
-- Left ()

instance Conv (M1 S s (K1 R ())) U1 where
  cv _ = U1
-- *> conv (Left () :: Either () Int) :: Maybe Int
-- Nothing

-- this one requires OverlappingInstances
instance (Generic c1, Generic c2, Conv (Rep c1) (Rep c2))
  => Conv (K1 R c1) (K1 R c2)
  where
    cv (K1 x) = K1 $ conv x
 -- *> conv (Right Nothing :: Either () (Maybe Int)) :: Maybe (Either () Int)
 -- Just (Left ())

 -- data List a = Empty | Cons a (List a) deriving (Generic, Show)
 -- *> conv [1,2,3::Int] :: List Int
 -- Cons 1 (Cons 2 (Cons 3 Empty))
于 2012-11-28T05:49:17.497 に答える