Reputation: 7327
Suppose I have a Haskell record like
data HaskellRecord = HaskellRecord {
_key1 :: Maybe String
, _key2 :: Maybe String
, _key3 :: Maybe String
}
Is there a way to construct a function
getKey :: HaskellRecord -> String -> Maybe String
such that with
let haskellRecord = HaskellRecord { _key1 = Just "value1"
, _key2 = Just "value2"
, _key3 = Just "value3"
}
then
getKey haskellRecord "value1" == Just "_key1"
getKey haskellRecord "value2" == Just "_key2"
getKey haskellRecord "value3" == Just "_key3"
getKey haskellRecord "value4" == Nothing
Upvotes: 1
Views: 328
Reputation: 153152
I guess I'd write something like this:
getKey :: String -> HaskellRecord -> [String]
getKey needle haystack =
[ name
| (name, selector) <- [("_key1", _key1), ("_key2", _key2), ("_key3", _key3)]
, selector haystack == Just needle
]
See it go:
> getKey "value1" haskellRecord
["_key1"]
...but I suspect this is an X-Y problem. (Because then what are you going to do with that String
? If the answer is "turn it back into a selector" or "use it to modify the appropriate field", then why not return a lens or something? And if you actually want a lens, then you probably don't even want this data structure in the first place...)
Upvotes: 7
Reputation: 51099
You can adapt my solution for a similar problem. It uses scrap-your-boilerplate (SYB) generics. That answer explains the code in a fair bit of detail, so I won't explain it here. Below is sample code that works with your example. It supports data types with a mixture of Maybe String
and String
fields; other field types are ignored by getKey
.
Note that this is not going to be very efficient. If this is a core part of your application logic, rather than some debugging hack, you should consider rethinking your data types. Maybe you don't actually want a Haskell record type here, but rather some kind of bi-directional map (e.g., a Map k v
and Map v k
paired together accessed through a set of functions that keep them consistent).
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
import Data.Tuple
import Data.Generics
data HaskellRecord = HaskellRecord {
_key1 :: Maybe String
, _key2 :: Maybe String
, _key3 :: Maybe String
} deriving (Data)
-- Get field names (empty list if not record constructor)
getnames :: Data object => object -> [String]
getnames = constrFields . toConstr
-- Get field values as `Maybe String`s
getfields :: Data object => object -> [Maybe String]
getfields = gmapQ toString
-- Generic function to convert one field.
toString :: (Data a) => a -> Maybe String
toString = mkQ Nothing -- make a query with default value Nothing
id -- handle: id :: Maybe String -> Maybe String
`extQ` Just -- extend to: Just :: String -> Maybe String
-- Get field name/value pairs from any `Data` object.
getpairs :: Data object => object -> [(String, Maybe String)]
getpairs = zip <$> getnames <*> getfields
getKey :: Data record => String -> record -> Maybe String
getKey v = lookup (Just v) . map swap . getpairs
main :: IO ()
main = do
print $ getKey "value2" $
HaskellRecord {
_key1 = Just "value1",
_key2 = Just "value2",
_key3 = Just "value3"
}
Upvotes: 3
Reputation: 48647
Of course you can write it by hand. It's easy, though slightly tedious and error-prone:
getKey (HaskellRecord (Just x) _ _) y | x == y = Just "_key1"
getKey (HaskellRecord _ (Just x) _) y | x == y = Just "_key2"
getKey (HaskellRecord _ _ (Just x)) y | x == y = Just "_key3"
getKey _ _ = Nothing
It looks like it's possible with Template Haskell or generics too, but there's not some pre-built way to do exactly this, so you'd have to write it yourself, and it'd be (obviously) a bit more complicated. The advantage would be that you'd only have to build it once, and not worry about changing it for data type changes.
Upvotes: 2