Haskell Code Generation
paradox generate --haskell [--path PATH]
Product Types
Input:
type Person
name: Text
age: Integer
admin: Boolean
Output:
data Person = Person
{ name :: Text
, age :: Integer
, admin :: Bool
}
deriving stock (Eq, Ord, Show, Read, Generic)
deriving anyclass (FromJSON, ToJSON)
Products derive Eq, Ord, Show, Read, Generic, FromJSON, and ToJSON automatically.
Union Types
Input:
union Cheese
farmers
cheddar
brie: Text
Output:
data Cheese = Farmers | Cheddar | Brie (Text)
deriving stock (Eq, Ord, Show, Read, Generic)
Custom FromJSON/ToJSON instances handle the tagged encoding:
instance FromJSON Cheese where
parseJSON v = parseString v <|> parseArray v
where
parseString = withText "Cheese" $ \case
"farmers" -> pure Farmers
"cheddar" -> pure Cheddar
s -> fail $ "Unknown nullary Cheese: " <> show s
parseArray = withArray "Cheese" $ \arr -> case toList arr of
[String "brie", val] -> Brie <$> parseJSON val
_ -> fail "Invalid Cheese array"
instance ToJSON Cheese where
toJSON Farmers = String "farmers"
toJSON Cheddar = String "cheddar"
toJSON (Brie v) = Array $ fromList [String "brie", toJSON v]
Nullary tags serialize as JSON strings; tags with payloads as [tag, value] arrays. This encoding is consistent across all targets.
Wrap Types
Input:
wrap Natural: Integer
Output:
newtype Natural = Natural { unNatural :: Integer }
deriving stock (Eq, Ord, Show, Read, Generic)
deriving newtype (FromJSON, ToJSON)
Wraps become Haskell newtypes with a record accessor.
Validators
validPerson :: Person -> Either Text Person
validPerson x =
let errors = catMaybes
[ if x.name == "" then Just "Pet's gotta have a name" else Nothing
, if x.age <= 0 then Just "Validation failed: age > 0" else Nothing
]
in if null errors then Right x else Left (T.intercalate "; " errors)
isValidPerson :: Person -> Bool
isValidPerson x = case validPerson x of
Left _ -> False
Right _ -> True
Module Naming
Source file product.dox generates module Dox.Product. The module includes a standard set of imports (Prelude, Aeson, Text, etc.) with NoImplicitPrelude to avoid conflicts.
Language Extensions
Generated code uses these extensions:
-
OverloadedRecordDot— for field access syntax -
OverloadedStrings— for Text literals -
DerivingStrategies+GeneralizedNewtypeDeriving+DeriveAnyClass -
DuplicateRecordFields+DisambiguateRecordFields -
LambdaCase -
NoImplicitPrelude+NoMonomorphismRestriction