module Domain.Docs ( -- * How it works {-| \"domain\" operates around Schema AST which describes the structure of your model. This AST gets constructed by either parsing a file or a quasi-quote conforming to a <#g:schemaSyntaxReference further described> format. Then it is used to generate Haskell type declarations and typeclass instances according to your configuration. All that is done at compile time, so you're incurring zero run time cost for using \"domain\". -} -- * Schema Syntax Reference #schemaSyntaxReference# {-| Schema definition is a YAML document listing declarations of your domain types. The listing is represented as a dictionary from type names to their definitions. There is 3 types of definitions: <#product Product>, <#sum Sum>, <#enum Enum>. -} -- ** Product #product# {-| Defines a type comprised of other types using , associating a unique textual label with each member. You may know it as \"record\". Here\'s an example of a product type declaration in schema: > NetworkAddress: > product: > protocol: TransportProtocol > host: Host > port: Word16 Depending on the settings you provide one of the following Haskell type declarations can be generated from it: > data NetworkAddress = > NetworkAddress !TransportProtocol !Host !Word16 > data NetworkAddress = > NetworkAddress { > networkAddressProtocol :: !TransportProtocol, > networkAddressHost :: !Host, > networkAddressPort :: !Word16 > } > data NetworkAddress = > NetworkAddress { > _protocol :: !TransportProtocol, > _host :: !Host, > _port :: !Word16 > } > data NetworkAddress = > NetworkAddress { > protocol :: !TransportProtocol, > host :: !Host, > port :: !Word16 > } -} -- *** Accessing fields #accessing-product-fields# {-| Regardless of the way you choose to generate the data declaration, neat mechanisms of accessing members can be provided using the automatically generated @IsLabel@ instances or instances of @LabelOptic@ (using the \"domain-optics\" package). E.g., here\'s how you can be accessing the members of the example data-type: > getNetworkAddressPort :: NetworkAddress -> Word16 > getNetworkAddressPort = #port > mapNetworkAddressHost :: (Host -> Host) -> NetworkAddress -> NetworkAddress > mapNetworkAddressHost = over #host -- Using "domain-optics" and "optics" -} -- ** Sum #sum# {-| Defines a type comprised of other types using , associating a unique textual label with each member. You may know it as tagged union or variant. Here\'s an example of a schema declaration of a sum type: > Host: > sum: > ip: Ip > name: Text The following Haskell code will be generated from it: > data Host = > IpHost !Ip | > NameHost !Text As you can see the constructor names are intentionally made to be unambiguous. You may already be thinking \"But the code is gonna get so verbose\". It\'s not. Thanks to the automatically generatable @IsLabel@ and @LabelOptic@ instances. E.g., here\'s how you\'ll be able to access the variants of the data-type: > getHostIp :: Host -> Maybe Ip > getHostIp = #ip > ipHost :: Ip -> Host > ipHost = #ip > mapHostIp :: (Ip -> Ip) -> Host -> Host > mapHostIp = over #ip -- Using "domain-optics" and "optics" -} -- *** Multi-member sums #multi-member-sums# {-| It is possible to provide multiple members of a sum variant using a comma-separated list or YAML sequence. You can provide zero members as well. E.g., > Error: > sum: > channel: > - ChannelId > - Text > connectionLost: This will generate the following declaration: > data Error = > ChannelError !ChannelId !Text | > ConnectionLostError Depending on the number of variant members the generated accessors will point to tuples or booleans: > getErrorChannel :: Error -> Maybe (ChannelId, Text) > getErrorChannel = #channel > > getErrorConnectionLost :: Error -> Bool > getErrorConnectionLost = #connectionLost -} -- ** Enum #enum# {-| Type which can have one value out of a specific set of options. Here\'s an example of a schema declaration of an enum type: > TransportProtocol: > enum: > - tcp > - udp This will generate the following Haskell data type: > data TransportProtocol = > TcpTransportProtocol | > UdpTransportProtocol The following 'IsLabel' helpers will be available for it: > tcpTransportProtocol :: TransportProtocol > tcpTransportProtocol = #tcp > > getTransportProtocolTcp :: TransportProtocol -> Bool > getTransportProtocolTcp = #tcp -} -- ** Notes #notes# -- *** List Data-type #list-data-type# {-| Since square brackets get interpreted in YAML as array literal, you have to explicitly state that the value is a string literal. To achieve that prefix the value with the vertical line character (@|@). E.g., > Artist: > product: > name: Text > genres: | [Genre] -} -- *** Reserved Names {-| You can use the otherwise banned field names like \"data\", \"type\", \"class\". -} -- *** Newtypes {-| Single-field products get represented as newtypes, so use them whenever you need to generate a newtype declaration. -} -- *** Type Aliases {-| Schemas intentionally lack support for type aliases, since they haven't yet proven to be very useful in practice. However we\'re open for discussion on the subject. So do provide your arguments on the project\'s issue tracker if you feel like they should be added as a feature. -} -- *** Polymorphic Types {-| Polymorphic types are not supported. Domain model is expected to consist of specific data structures, not abstractions. -} -- * Instance Derivation {-| Instance derivation is intentionally isolated from the schema definition to let both tasks be focused. Instances get derived for all the types in your schema that they are suitable for. We treat schema as a group entity over multiple types having them share settings including the instance generation rules. Whenever you find yourself in a situation where you need different instances for parts of your model it should serve as a signal that you\'re likely dealing with multiple models merged into one. The solution to such situation is to extract smaller models. When dealing with Domain Schema that is what will also let you generate different instances. -} -- ** Custom Derivers {-| The \"domain\" package does not expose any means to create custom derivers, since its API focuses on their usage as part of the problems of the general audience. To create custom derivers you\'ll have to use the ["domain-core"](http://hackage.haskell.org/package/domain-core) package, which exposes the internal definition of the 'DomainCore.Deriver.Deriver' abstraction and everything you need to define custom derivers. Such isolation of libraries lets us have a stable API for the general audience, serving for better backward compatibility, and keep it isolated from the distractions of lower level details. -} -- ** Deriver Extensions {-| We expect the community to publish their general custom derivers as extensional packages. So far there is one package known (which we\'ve published ourselves): - ["domain-optics"](http://hackage.haskell.org/package/domain-optics) - provides integration with the ["optics"](http://hackage.haskell.org/package/optics) package. If you\'re looking to contribute, some likely needed candidates for extensions are \"QuickCheck\", \"aeson\", \"binary\", \"cereal\". -} ) where import Domain.Prelude hiding (liftEither, readFile, lift) import Domain