简体   繁体   中英

How to use Haskell generics to populate a record?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM