2017-01-07 8 views
3

単純な代数データ(本質的に列挙型)と、フィールドとしてこれらの列挙型を持つ別の型があるとします。ハスケル:代数的データ型に「マッピング」する方法はありますか?

data Color = Red | Green | Blue deriving (Eq, Show, Enum, Ord) 
data Width = Thin | Normal | Fat deriving (Eq, Show, Enum, Ord) 
data Height = Short | Medium | Tall deriving (Eq, Show, Enum, Ord) 

data Object = Object { color :: Colour 
        , width :: Width 
        , height :: Height } deriving (Show) 

オブジェクトのリストが与えられている場合、属性がすべて異なることをテストしたいと思います。このために、私は(Data.Listからsortを使用して)機能

allDifferent = comparePairwise . sort 
    where comparePairwise xs = and $ zipWith (/=) xs (drop 1 xs) 

uniqueAttributes :: [Object] -> Bool 
uniqueAttributes objects = all [ allDifferent $ map color objects 
           , allDifferent $ map width objects 
           , allDifferent $ map height objects ] 

を以下しているこれは動作しますが、私は手動で各フィールド(色、幅、高さ)を入力しなければならなかったので、かなり不満です。私の実際のコードでは、より多くのフィールドがあります! 「マッピング」関数Objectのような代数的データ型のフィールドを超える

\field -> allDifferent $ map field objects 

の方法はありますか?ここで

+0

スクラップ用の定型文を使用できます。この単純なケースでは、それがはるかに良いかどうかはわかりません。 – chi

+1

ジェネリックでなくても、多少の影響を受けます: 'uniqueAttributes objects = and [go color、go width、go height] go ::(Ord a)=>(Object - > a) - > Bool; go f = allDifferent(マップfオブジェクト) ' –

答えて

4

generics-sopを使用してソリューションです...私は、そのフィールドのリストとして(例えば、JavaScriptで簡単だろう何かを)Objectを治療したいのですが、これらのフィールドは、さまざまな種類があります。

pointwiseAllDifferent 
    :: (Generic a, Code a ~ '[ xs ], All Ord xs) => [a] -> Bool 
pointwiseAllDifferent = 
    and 
    . hcollapse 
    . hcmap (Proxy :: Proxy Ord) (K . allDifferent) 
    . hunzip 
    . map (unZ . unSOP . from) 

hunzip :: SListI xs => [NP I xs] -> NP [] xs 
hunzip = foldr (hzipWith ((:) . unI)) (hpure []) 

この比較したいObjectタイプがレコード型であり、あなたがこのタイプにテンプレートHaskellのを使用して行うことができるクラスGeneric、のインスタンスを作成することを必要とすることを前提としています

deriveGeneric ''Object 

のだか見てみましょう具体的な例を見て、ここで起こっ:hunzipその後、ターン

GHCi> map (unZ . unSOP . from) objects 
[I Red :* (I Thin :* (I Short :* Nil)),I Green :* (I Fat :* (I Short :* Nil))] 

objects = [Object Red Thin Short, Object Green Fat Short] 

ラインmap (unZ . unSOP . from)は、(ライブラリ内のn進品と呼ばれる)異種リストに各Objectを変換し、各要素はリストで、製品への製品のリスト:今

GHCi> hunzip it 
[Red,Green] :* ([Thin,Fat] :* ([Short,Short] :* Nil)) 

は、我々はallDifferentを適用します

GHCi> hcmap (Proxy :: Proxy Ord) (K . allDifferent) it 
K True :* (K True :* (K False :* Nil)) 

製品は、すべての位置はBool含まれているとして、実際には今均一であるので、hcollapseは再び通常の均質なリストに変換します:

GHCi> hcollapse it 
[True,True,False] 

最後のステップを製品内の各リストへちょうどそれにandを適用します。この非常に特定の状況については

GHCi> and it 
False 
0

(単純合算TYPある属性のセットをチェックします0-アリティのコンストラクタを持つES)は、あなたがData.Dataジェネリックを使用して以下の構成を使用することができます。

{-# LANGUAGE DeriveDataTypeable #-} 

module Signature where 

import Data.List (sort, transpose) 
import Data.Data 

data Color = Red | Green | Blue deriving (Eq, Show, Enum, Ord, Data) 
data Width = Thin | Normal | Fat deriving (Eq, Show, Enum, Ord, Data) 
data Height = Short | Medium | Tall deriving (Eq, Show, Enum, Ord, Data) 

data Object = Object { color :: Color 
        , width :: Width 
        , height :: Height } deriving (Show, Data) 

-- |Signature of attribute constructors used in object 
signature :: Object -> [String] 
signature = gmapQ (show . toConstr) 

uniqueAttributes :: [Object] -> Bool 
uniqueAttributes = all allDifferent . transpose . map signature 

allDifferent :: (Ord a) => [a] -> Bool 
allDifferent = comparePairwise . sort 
    where comparePairwise xs = and $ zipWith (/=) xs (drop 1 xs) 

ここで重要なのは、オブジェクトを受け取り、一般的にその直下の子を挟ん子のコンストラクタの名前を計算する関数signatureです。だから、:

*Signature> signature (Object Red Fat Medium) 
["Red","Fat","Medium"] 
*Signature> 

これらの単純な和型以外のすべてのフィールドがある場合

は、(のようなタイプ data Weight = Weight Intの属性を言うか、あなたは Objectname :: Stringフィールドを追加した場合)、これは、突然失敗します。

(これはあまり間接的に感じているならば、あなたは基本的には、インデックスがdata定義内のコンストラクタの1から始まるInt -valuedコンストラクタ指数を(使用するshow . toConstrの代わりにconstrIndex . toConstrを使用することができます:)注)を追加するために編集。 toConstrによって返されたConstrOrdインスタンスがある場合、間接指定はありませんが、残念なことに...

+0

コンストラクタを文字列に変換し、それを比較することは幾分間接的ですが、この解決方法は非常に単純です。ジェネリックへの新しいアプローチがすべて激怒しているようだが、 'Data.Data'がその仕事をしている! – yun

+0

代わりに 'Int'値のインデックスを取得するために' constrIndex'を使うことについての注意を追加しました。これは私がもともとしたことですが、 'show'を使うと、(直接的かつ効率的ではありませんが)より良い署名値が得られます。 –

関連する問題