Let's say I have a data structure like the following:
[("a", "123"), ("b", "234")]
and I want to populate a record like this:
data R = R { a :: Int, b :: String }
so that I end up with a record r { a = 123, b = "234" }
without having to manually write conversion code.
Various packages (like aeson and some of the command line parsing packages) do something like this and I'd like to know about the available mechanisms that let me do this and how they are used. I've heard about Data.Data
, Data.Typeable
and a few others but don't know how these work to make the magic happen.
The GHC.Generics docs have a straightforward tutorial which can get you 99% of the way there. Here's some code based on that tutorial:
{-# LANGUAGE DeriveGeneric, TypeOperators #-}
{-# LANGUAGE DefaultSignatures, FlexibleContexts, FlexibleInstances, ScopedTypeVariables #-}
{-# LANGUAGE KindSignatures #-}
import GHC.Generics
import Data.List (lookup)
import Data.Proxy
class FromKV' f where
fromKV' :: [(String, String)] -> f p
instance FromKV' V1 where
fromKV' _ = error "fromKV' V1"
instance FromKV' U1 where
fromKV' _ = U1
instance (FromKV c) => FromKV' (K1 i c) where
fromKV' = K1 . fromKV
-- http://stackoverflow.com/a/24474958/477476
data SelectorProxy s (f :: * -> *) a = SelectorProxy
type SelectorProxyFor s = SelectorProxy s Proxy ()
instance (Read a, Selector s) => FromKV' (M1 S s (Rec0 a)) where
fromKV' = M1 . K1 . maybe (error $ unwords ["fromKV' : missing key:", key]) read . lookup key
where
key = selName (SelectorProxy :: SelectorProxyFor s)
instance (FromKV' f) => FromKV' (D1 t f) where
fromKV' = M1 . fromKV'
instance (FromKV' f) => FromKV' (C1 t f) where
fromKV' = M1 . fromKV'
instance (FromKV' f, FromKV' g) => FromKV' (f :*: g) where
fromKV' kvs = fromKV' kvs :*: fromKV' kvs
class FromKV a where
fromKV :: [(String, String)] -> a
default fromKV :: (Generic a, FromKV' (Rep a)) => [(String, String)] -> a
fromKV = to . fromKV'
data MyR = MyR{ a :: Int, b :: String }
deriving (Show, Generic)
instance FromKV MyR
The missing 1% (but that is somewhat orthogonal to your question anyway) is changing the FromKV' (M1 S s (Rec0 a)
instance so that String
s are consumed verbatim instead of read
ing them: with the above code, this works:
*Main> fromKV [("a", "123"), ("b", "\"234\"")] :: MyR
MyR {a = 123, b = "234"}
but this doesn't:
*Main> fromKV [("a", "123"), ("b", "234")] :: MyR
MyR {a = 123, b = "*** Exception: Prelude.read: no parse
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.