正在寻找这个问题的答案:
https://stackoverflow.com/a/34164251/1052117
我看到它定义了一种用于解析JSON对象的数据类型。
data Address = Address
{ house :: Integer
, street :: String
, city :: String
, state :: Maybe String
, zip :: String -- here I change the original, zip codes are strings, they have leading zeros.
} deriving (Show, Eq)
$(deriveJSON defaultOptions ''Address)
这很有用,但是我想知道:如何更改Address数据类型以使所有json字段都可以为空?具体来说,我在状态字段之前看到了一个Maybe,但是我正在想象一个更大的数据结构,将所有字段修改为Maybe字段将很繁琐。例如,当我/可以/上面改写为:
data Address = Address
{ house :: Maybe Integer
, street :: Maybe String
, city :: Maybe String
, state :: Maybe String
, zip :: Maybe String
} deriving (Show, Eq)
在不重写所有代码和手动插入Maybes的情况下,我可以对Address数据类型/ in code /应用什么功能以达到相同的结果?
答案 0 :(得分:5)
正如评论中所讨论的那样,使用functor-functor可以对原始数据类型进行很小的更改。
如果您以
开始data Address = Address
{ house :: Integer
, street :: String
, city :: String
, state :: Maybe String
, zip :: String
} deriving (Show, Eq)
那么等同于
import Data.Functor.Identity
data AddressF f = Address
{ house :: f Integer
, street :: f String
, city :: f String
, state :: Maybe String
, zip :: f String
} deriving (Show, Eq)
type Address = AddressF Identity
然后您可以通过编写
获得第二个type Address' = AddressF Maybe
要回到原始定义,您可以编写
toOriginal (AddressF (Identity house) (Identity street) (Identity city) mbState (Identity zip)) = Address house street city mbState zip
答案 1 :(得分:2)
获得相同的结果,而无需手动重写所有代码 插入Maybes
为避免对记录类型进行侵入式更改,我们可以通过分析记录的结构来处理来自记录 的另一种类型,这需要相对高级的泛型和类型级别的编程。该答案使用generics-sop软件包。
一些必需的语用和输入:
{-# LANGUAGE DataKinds, TypeFamilies, FlexibleInstances, UndecidableInstances,
ScopedTypeVariables, TypeApplications, TypeOperators,
DeriveGeneric, StandaloneDeriving, MultiParamTypeClasses,
FunctionalDependencies, AllowAmbiguousTypes, FlexibleContexts #-}
import Data.Kind (Type)
import Data.Type.Equality (type (==))
import GHC.TypeLits
import qualified GHC.Generics as GHC
import Generics.SOP -- from package "generics-sop"
import qualified Generics.SOP.Type.Metadata as M
此新类型表示从记录派生的字段值n-ary product,每个字段值都包装在函子f
中。字段names的类型级别列表ns
被保存为phantom type variable:
newtype Wrapped f (ns :: [Symbol]) (xs :: [Type]) = Wrapped { unwrap :: NP f xs }
deriving instance All (Generics.SOP.Compose Show f) xs => Show (Wrapped f ns xs)
type family FieldNamesOf (a :: M.DatatypeInfo) :: [Symbol] where
FieldNamesOf ('M.ADT moduleName datatypeName '[ 'M.Record constructorName fields ]) =
ExtractFieldNames fields
type family ExtractFieldNames (a :: [M.FieldInfo]) :: [Symbol] where
ExtractFieldNames '[] = '[]
ExtractFieldNames (('M.FieldInfo n) ': xs) = n ': ExtractFieldNames xs
fromRecord :: forall r ns xs. (IsProductType r xs,
HasDatatypeInfo r,
FieldNamesOf (DatatypeInfoOf r) ~ ns)
=> r
-> Wrapped I ns xs
fromRecord r = let (SOP (Z np)) = from r in Wrapped np
toRecord :: forall r ns xs. (IsProductType r xs,
HasDatatypeInfo r,
FieldNamesOf (DatatypeInfoOf r) ~ ns)
=> Wrapped I ns xs
-> r
toRecord (Wrapped np) = to (SOP (Z np))
如果我们不需要保留字段名,那么新类型将变得多余,最好直接使用n元乘积NP,并使用由提供的rich set of functions对其进行操作。泛型-sop。
但是,如果我们确实想保持按名称选择字段的能力,那么我们需要在newtype上定义一个函数,该函数受一对typeclasses支持:
getWrappedField :: forall n f ns xs x. HasField ns n xs x => Wrapped f ns xs -> f x
getWrappedField (Wrapped np) = getHasField @ns @n np
class HasField (ns :: [Symbol]) (n :: Symbol)
(xs :: [Type]) (x :: Type) | ns n xs -> x where
getHasField :: NP f xs -> f x
instance ((e == n) ~ flag, HasField' flag (e : ns) n xs x) => HasField (e : ns) n xs x where
getHasField = getHasField' @flag @(e : ns) @n
class HasField' (flag :: Bool)
(ns :: [Symbol]) (n :: Symbol)
(xs :: [Type]) (x :: Type) | ns n xs -> x where
getHasField' :: NP f xs -> f x
instance HasField' True (n : ns) n (x : xs) x where
getHasField' (v :* _) = v
instance HasField ns n xs x => HasField' False (nz : ns) n (xz : xs) x where
getHasField' (_ :* rest) = getHasField @ns @n rest
鉴于此示例记录得出了necessary supporting typeclasses:
data Person = Person { name :: String, age :: Int } deriving (Show, GHC.Generic)
instance Generic Person
instance HasDatatypeInfo Person
我们可以构造其广义表示形式(所有字段最初都包装在身份函子I中),然后获得一个字段,如下所示:
ghci> getWrappedField @"age" (fromRecord (Person "Jimmy" 25))
I 25
使用type application将字段名称作为类型级别Symbol
传递。