2011-12-28 24 views
10

Say mam następujący zapis:Haskell dynamicznie ustawia pole rekordu na podstawie ciągu nazwy pola?

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

Jak napisać funkcję:

changeField :: Rec -> String -> Int -> Rec 
changeField rec fieldName value 

takie, że mogę przechodzić w struny "pole1" lub "Pole2" do fieldName argument i czy aktualizuje powiązane pole? Rozumiem, że w tym przypadku należy używać Data.Data i Data.Typeable, ale nie mogę wykreślić tych dwóch pakietów.


Przykładem biblioteki, którą widziałem, jest to cmdArgs. Poniżej znajduje się excerpt z delegowania blogu o tym, jak korzystać z tej biblioteki:

{-# 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 

Teraz mamy proste wiersza polecenia parsera. Niektóre przykładowe interakcje są:

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

Prawdopodobnie nie chcesz tego robić. Czy słyszałeś o [soczewkach] (http://stackoverflow.com/questions/5767129/lenses-fclabels-data-accessor-which-library- for-structure-access-and-mutatio)? Myślę, że jedynym sposobem na osiągnięcie tego byłoby hack polegający na parowaniu nazw pól z ich wskaźnikami argumentów i użyciem 'gmapQi' lub podobnego. (Musiałbyś dodać "wyprowadzenie (możliwe do zdobycia, dane)" do deklaracji rekordu, aby mieć jakąkolwiek nadzieję na działanie, nie można tego zrobić dla arbitralnych typów.) – ehird

+1

Chcę to zrobić. Chciałbym utworzyć bibliotekę, w której użytkownik może dostarczyć rekord, a biblioteka może zapełnić rekord, analizując tekst. Tekst będzie zawierał odniesienia do pola w rekordzie, które chcę ustawić. – Ana

+0

Najlepiej jest unikać powiązania implementacji tej funkcji użytkownika z wewnętrznymi szczegółami implementacji nazw pól rekordu. Po drugie zasugerowałem rozwiązanie oparte na soczewce @pat; można zautomatyzować tworzenie 'recMap' z nazw pól rekordów za pomocą szablonu Haskell. – ehird

Odpowiedz

6

OK, oto rozwiązanie, które nie robi użyj szablonu haskell lub wymagaj ręcznego zarządzania mapą pola.

zaimplementowałem bardziej ogólne modifyField która przyjmuje funkcję modyfikujące i realizowane setField (nee changeField) przy jej const value.

Podpis modifyField i setField jest generyczny zarówno w typie rekordu, jak i typu mutator/value; Aby jednak uniknąć niejednoznaczności Num, stałe numeryczne w przykładach wywołania muszą mieć wyraźne podpisy :: Int.

również zmieniło kolejność parametrów tak rec jest ostatni, dzięki czemu łańcuch modifyField/setField być tworzony przez normalną funkcją składu (patrz ostatni przykład wywołania).

modifyField jest zbudowany na podstawie pierwotnego gmapTi, który jest "brakującą" funkcją z Data.Data. Jest to skrzyżowanie gmapT i gmapQi.

{-# 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

można zbudować mapę z nazwami pól do swoich obiektywów:

{-# 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'' 

lub bez soczewek:

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 to dokładnie przedmiot, którego unikam. Wymagam ode mnie specjalizacji dla każdego pola i chciałbym dynamicznie wykonywać mapowanie z ciągu na pole. – Ana

Powiązane problemy