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