Say I have the following record:
data Rec = Rec {
field1 :: Int,
field2 :: Int
}
How do I write the function:
changeField :: Rec -> String -> Int -> Rec
changeField rec fieldName value
such that I can pass in the strings "field1" or "field2" into the fieldName
argument and have it update the associated field? I understand Data.Data
and Data.Typeable
are what to use here but I can't figure these two packages out.
An example of a library I've seen do this is cmdArgs. Below is an excerpt from a blog posting on how to use this library:
{-# 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
Now we have a simple command line parser. Some sample interactions are:
$ guess --min=10
NumberGuess {min = 10, max = 100, limit = Nothing}
You can build a map from the field names to their lenses:
or without lenses:
OK, here's a solution that doesn't use template haskell, or require you to manage the field map manually.
I implemented a more general
modifyField
which accepts a mutator function, and implementedsetField
(neechangeField
) using it withconst value
.The signature of
modifyField
andsetField
is generic in both the record and mutator/value type; however, in order to avoidNum
ambiguity, the numeric constants in the invocation example have to be given explicit:: Int
signatures.I also changed the parameter order so
rec
comes last, allowing a chain ofmodifyField
/setField
to be created by normal function composition (see the last invocation example).modifyField
is built on top of the primitivegmapTi
, which is a 'missing' function fromData.Data
. It is a cross betweengmapT
andgmapQi
.