2011-12-28 11 views
10

は、私は以下のレコードを持っていると言う:Haskellはフィールド名文字列に基づいてレコードフィールドを動的に設定しますか?

data Rec = Rec { 
    field1 :: Int, 
    field2 :: Int 
} 

私は関数を書くにはどうすればよい:

私は fieldName引数に文字列「フィールド1」または「フィールド2」に渡すことができるような
changeField :: Rec -> String -> Int -> Rec 
changeField rec fieldName value 

とそれは関連フィールドを更新してもらえますか?私はData.DataData.Typeableをここで使うものと理解していますが、私はこれらの2つのパッケージを理解することはできません。


私が見たことのあるライブラリの例は、cmdArgsです。以下は、このライブラリを使用する方法についてのブログ投稿からexcerpt次のとおりです。

{-# LANGUAGE DeriveDataTypeable #-} 
import System.Console.CmdArgs 

data Guess = Guess {min :: Int, max :: Int, limit :: Maybe Int} deriving (Data,Typeable,Show) 

main = do 
    x <- cmdArgs $ Guess 1 100 Nothing 
    print x 

今、私たちは、単純なコマンドラインパーサを持っています。いくつかのサンプルの相互作用は、次のとおりです。

$ guess --min=10 
NumberGuess {min = 10, max = 100, limit = Nothing} 
+2

あなたはおそらくこれをしたくありません。 [レンズ](http://stackoverflow.com/questions/5767129/lenses-fclabels-data-accessor-which-library-for-structure-access-and-mutatio)について聞いたことがありますか?私は、これを達成する唯一の方法は、フィールド名を引数のインデックスと組み合わせ、 'gmapQi'などを使ってハックすることだと思います。 (これはあなたのレコード宣言に 'deriving(Typeable、Data)'を追加する必要があります。これは任意の型に対しては実行できません) – ehird

+1

私はこれをしたいと思います。ユーザーがレコードを提供できるライブラリを作成したいと思います。ライブラリはテキストを解析することでレコードを作成できます。テキストには、設定したいレコードのフィールドへの参照が含まれます。 – Ana

+0

このユーザー対応機能の実装をレコードフィールド名の内部実装の詳細に結びつけないようにすることをお勧めします。私はレンズベースのソリューションの2番目を提案しました。 Template Haskellを使ってレコードフィールド名から 'recMap'の作成を自動化することができます。 – ehird

答えて

6

OKは、ここではdoesnの解決策ですテンプレートhaskellを使用しないか、フィールドマップを手動で管理する必要があります。

Iはミューテータ関数を受け入れ、より一般的なmodifyFieldを実装し、const valueでそれを使用setField(旧姓changeField)を実施しました。

modifyFieldsetFieldのシグネチャは、レコードとミューテータ/値型の両方で一般的です。ただし、Numのあいまいさを避けるためには、呼び出し例の数値定数に明示的に:: Intシグニチャを指定する必要があります。

Iは、(最後の呼び出し例を参照)のでrecmodifyField/setFieldの鎖は、正常な機能組成物によって作成されることを可能にする、最後に来るパラメータの順序を変更しました。

modifyFieldは、Data.Dataの 'missing'関数であるgmapTiの上に構築されます。それはgmapTgmapQiの間のクロスです。

{-# LANGUAGE DeriveDataTypeable #-} 
{-# LANGUAGE RankNTypes #-} 

import Data.Typeable (Typeable, typeOf) 
import Data.Data (Data, gfoldl, gmapQi, ConstrRep(AlgConstr), 
        toConstr, constrRep, constrFields) 
import Data.Generics (extT, extQ) 
import Data.List (elemIndex) 
import Control.Arrow ((&&&)) 

data Rec = Rec { 
    field1 :: Int, 
    field2 :: String 
} deriving(Show, Data, Typeable) 

main = do 
    let r = Rec { field1 = 1, field2 = "hello" } 
    print r 
    let r' = setField "field1" (10 :: Int) r 
    print r' 
    let r'' = setField "field2" "world" r' 
    print r'' 
    print . modifyField "field1" (succ :: Int -> Int) . setField "field2" "there" $ r 
    print (getField "field2" r' :: String) 

--------------------------------------------------------------------------------------- 

data Ti a = Ti Int a 

gmapTi :: Data a => Int -> (forall b. Data b => b -> b) -> a -> a 
gmapTi i f x = case gfoldl k z x of { Ti _ a -> a } 
    where 
    k :: Data d => Ti (d->b) -> d -> Ti b 
    k (Ti i' c) a = Ti (i'+1) (if i==i' then c (f a) else c a) 
    z :: g -> Ti g 
    z = Ti 0 

--------------------------------------------------------------------------------------- 

fieldNames :: (Data r) => r -> [String] 
fieldNames rec = 
    case (constrRep &&& constrFields) $ toConstr rec of 
    (AlgConstr _, fs) | not $ null fs -> fs 
    otherwise       -> error "Not a record type" 

fieldIndex :: (Data r) => String -> r -> Int 
fieldIndex fieldName rec = 
    case fieldName `elemIndex` fieldNames rec of 
    Just i -> i 
    Nothing -> error $ "No such field: " ++ fieldName 

modifyField :: (Data r, Typeable v) => String -> (v -> v) -> r -> r 
modifyField fieldName m rec = gmapTi i (e `extT` m) rec 
    where 
    i = fieldName `fieldIndex` rec 
    e x = error $ "Type mismatch: " ++ fieldName ++ 
          " :: " ++ (show . typeOf $ x) ++ 
          ", not " ++ (show . typeOf $ m undefined) 

setField :: (Data r, Typeable v) => String -> v -> r -> r 
setField fieldName value = modifyField fieldName (const value) 

getField :: (Data r, Typeable v) => String -> r -> v 
getField fieldName rec = gmapQi i (e `extQ` id) rec 
    where 
    i = fieldName `fieldIndex` rec 
    e x = error $ "Type mismatch: " ++ fieldName ++ 
          " :: " ++ (show . typeOf $ x) ++ 
          ", not " ++ (show . typeOf $ e undefined) 
4

あなたは自分のレンズにフィールド名からマップを構築することができます

{-# LANGUAGE TemplateHaskell #-} 
import Data.Lens 
import Data.Lens.Template 
import qualified Data.Map as Map 

data Rec = Rec { 
    _field1 :: Int, 
    _field2 :: Int 
} deriving(Show) 

$(makeLens ''Rec) 

recMap = Map.fromList [ ("field1", field1) 
         , ("field2", field2) 
         ] 

changeField :: Rec -> String -> Int -> Rec 
changeField rec fieldName value = set rec 
    where set = (recMap Map.! fieldName) ^= value 

main = do 
    let r = Rec { _field1 = 1, _field2 = 2 } 
    print r 
    let r' = changeField r "field1" 10 
    let r'' = changeField r' "field2" 20 
    print r'' 

またはレンズなし:

import qualified Data.Map as Map 

data Rec = Rec { 
    field1 :: Int, 
    field2 :: Int 
} deriving(Show) 

recMap = Map.fromList [ ("field1", \r v -> r { field1 = v }) 
         , ("field2", \r v -> r { field2 = v }) 
         ] 

changeField :: Rec -> String -> Int -> Rec 
changeField rec fieldName value = 
    (recMap Map.! fieldName) rec value 

main = do 
    let r = Rec { field1 = 1, field2 = 2 } 
    print r 
    let r' = changeField r "field1" 10 
    let r'' = changeField r' "field2" 20 
    print r'' 
+1

recMapは私が避けているアイテムです。私はすべての分野に特化する必要があり、文字列からフィールドへのマッピングを動的にしたいと考えています。 – Ana

関連する問題