Manera idiomática de definir tipos nuevos versus tipos persistentes en Haskell
Tengo un tipo que representa un registro persistente. Quiero tener un tipo muy similar que represente datos que deben publicarse para crear un nuevo registro.
Este es el tipo completo:
data Record = Reading
{ id: UUID
, value: String
...
}
el tipo "nuevo" es el mismo menos el "id", que será generado automáticamente por la base de datos. ¿Cómo puedo definir este tipo? Estoy usando un servidor para definir la API.
Mi estrategia actual es anteponer el tipo y todos los campos con "nuevo", lo cual funciona pero es redundante para modelos de muchos campos. También he visto la estrategia anidada donde tengo un tipo compartido común. También pensé en hacer que la identificación sea opcional, pero realmente no quiero poder publicarla.
Puede implementar esto con un enfoque similar al de datos de tipo superior .
Primero, algunas importaciones:
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module Example where
import Data.Functor.Identity
import Data.Proxy
import Data.UUID
import Data.Aeson
import GHC.Generics
Luego, defina un registro con un parámetro de tipo superior:
data Record f = Record { recordId :: f UUID, recordValue :: String } deriving (Generic)
El Identity
Functor le ofrece una variante de este registro que siempre tiene una identificación.
type RecordWithId = Record Identity
El uso Maybe
le brinda una variante con una identificación opcional .
type RecordWithOptionalId = Record Maybe
Proxy
se puede utilizar como Functor con un único valor de "unidad" poco interesante. (y sin valores del tipo envuelto). Esto nos permite crear un tipo para Record
sin ID.
type RecordWithoutId = Record (Proxy)
Podemos derivar Show
para nuestro Record
.
deriving instance (Show (f UUID)) => Show (Record f)
Se requiere pasar omitNothingFields = True
y allowOmitedFields = True
en los Aeson
casosRecordWithoutId
para analizar a como era de esperar. Esto requiere una versión de Aeson >= 2.2.0.0 (que al momento de escribir este artículo es más reciente que la última Stackage Snapshot). Probablemente podría implementar las instancias de Aeson a mano si esta versión no funciona para usted.
instance (ToJSON (f UUID)) => ToJSON (Record f) where
toJSON = genericToJSON defaultOptions { omitNothingFields = True, allowOmitedFields = True }
instance (FromJSON (f UUID)) => FromJSON (Record f) where
parseJSON = genericParseJSON defaultOptions { omitNothingFields = True, allowOmitedFields = True }
Codificando un valor con un ID:
ghci> import qualified Data.ByteString.Lazy.Char8 as BL
ghci> BL.putStrLn $ encode (Record {recordId = Identity nil, recordValue = "value" })
{"recordId":"00000000-0000-0000-0000-000000000000","recordValue":"value"}
Codificando un valor sin ID:
ghci> BL.putStrLn $ encode (Record {recordId = Proxy, recordValue = "value" })
{"recordValue":"value"}
Decodificar un valor sin ID
ghci> decode "{\"recordValue\":\"value\"}" :: Maybe RecordWithoutId
Just (Record {recordId = Proxy, recordValue = "value"})