{"id":16732944,"url":"https://github.com/nikita-volkov/domain","last_synced_at":"2025-03-17T01:31:47.557Z","repository":{"id":53608112,"uuid":"297778719","full_name":"nikita-volkov/domain","owner":"nikita-volkov","description":"Focused domain model declaration toolkit for Haskell","archived":false,"fork":false,"pushed_at":"2023-12-09T13:11:35.000Z","size":164,"stargazers_count":47,"open_issues_count":2,"forks_count":1,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-04-25T22:30:30.696Z","etag":null,"topics":["boilerplate","codegen","haskell","model","template-haskell"],"latest_commit_sha":null,"homepage":"http://hackage.haskell.org/package/domain","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nikita-volkov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-09-22T21:40:09.000Z","updated_at":"2024-04-17T14:24:52.000Z","dependencies_parsed_at":"2024-10-12T23:47:44.952Z","dependency_job_id":"a92acecb-9d9b-43af-b645-08ba49c40ff2","html_url":"https://github.com/nikita-volkov/domain","commit_stats":{"total_commits":171,"total_committers":1,"mean_commits":171.0,"dds":0.0,"last_synced_commit":"062d559eed4dab79b2b4eaf8f3e956d9db730d0b"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikita-volkov%2Fdomain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikita-volkov%2Fdomain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikita-volkov%2Fdomain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikita-volkov%2Fdomain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nikita-volkov","download_url":"https://codeload.github.com/nikita-volkov/domain/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243836015,"owners_count":20355615,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["boilerplate","codegen","haskell","model","template-haskell"],"created_at":"2024-10-12T23:47:42.525Z","updated_at":"2025-03-17T01:31:47.225Z","avatar_url":"https://github.com/nikita-volkov.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# About\n\nTemplate Haskell codegen removing noise and boilerplate from domain models.\n\n# Problem\n\nImagine a real-life project, where you have to define the types for your problem domain: your domain model. How many types do you think there'll be? [A poll among Haskellers](https://twitter.com/NikitaYVolkov/status/1324360237827108870) shows that highly likely more than 30. That is 30 places for you to derive or define instances, work around the records problem and the problem of conflicting constructor names. That is a lot of boilerplate and noise, distracting you from your actual goal of modeling the data structures or learning an existing model during maintenance. Also don't forget about the boilerplate required to generate optics for your model to actually make it accessible.\n\n# Mission\n\nIn its approach to those problems this project sets the following goals:\n\n- Let the domain model definition be focused on data and nothing else.\n- Let it be readable and comfortably editable, avoiding syntactic noise.\n- Separate its declaration from the problems of declaration of instances, accessor functions, optics and etc.\n- Have the records problem solved.\n- Have the problem of conflicting constructor names solved.\n- Avoid boilerplate in all the above.\n- Avoid complications of the build process.\n\n# Solution\n\nThis project introduces a clear boundary between the data model declaration and the rest of the code base.\nIt introduces a YAML format designed specifically for the problem of defining types and relations between them and that only. We call it Domain Schema.\n\nSchemas can be loaded at compile time and transformed into Haskell declarations using Template Haskell. Since it's just Template Haskell, no extra build software is needed to use this library. It is a normal Haskell package.\n\nSchema gets analysed allowing to generate all kinds of instances automatically using a set of prepackaged derivers. An API is provided for creation of custom derivers for extending the library or handling special cases.\n\n# Tutorial and Case in Point\n\nWe'll show you how this whole thing works on an example of a model of a service address.\n\n## Schema\n\nFirst we need to define a schema. For that we create the following YAML document:\n\n```yaml\n# Service can be either located on the network or\n# by a socket file.\n#\n# Choice between two or more types can be encoded using\n# \"sum\" type composition, which you may also know as\n# \"union\" or \"variant\". That's what we use here.\nServiceAddress:\n  sum:\n    network: NetworkAddress\n    local: FilePath\n\n# Network address is a combination of transport protocol,\n# host and port. All those three things at once.\n#\n# \"product\" type composition lets us encode that.\n# You may also know it as \"record\" or \"tuple\".\nNetworkAddress:\n  product:\n    protocol: TransportProtocol\n    host: Host\n    port: Word16\n\n# Transport protocol is either TCP or UDP.\n# We encode that using enumeration.\nTransportProtocol:\n  enum:\n    - tcp\n    - udp\n\n# Host can be adressed by either an IP or its name,\n# so \"sum\" again.\nHost:\n  sum:\n    ip: Ip\n    name: Text\n\n# IP can be either of version 4 or version 6.\n# We encode it as a sum over words of the accordingly required\n# amount of bits.\nIp:\n  sum:\n    v4: Word32\n    v6: Word128\n\n# Since the standard lib lacks a definition\n# of a 128-bit word, we define a custom one\n# as a product of two 64-bit words.\nWord128:\n  product:\n    part1: Word64\n    part2: Word64\n```\n\nAs you can see in the specification above we're not concerned with typeclass instances or problems of name disambiguation. We're only concerned with data and relations that it has. This is what we mean by focus. It makes the experience of designing and maintaining a model distraction free.\n\nThose three methods of defining types (product, sum, enum) are all that you need to define a model of any complexity. If you understand them, there's nothing new to learn.\n\n### Codegen\n\nNow, having that schema defined in a file at path `schemas/model.yaml`,\nwe can load it in a Haskell module as follows:\n\n```haskell\n{-# LANGUAGE\n  TemplateHaskell,\n  StandaloneDeriving, DeriveGeneric, DeriveDataTypeable, DeriveLift,\n  FlexibleInstances, MultiParamTypeClasses,\n  DataKinds, TypeFamilies\n  #-}\nmodule Model where\n\nimport Data.Text (Text)\nimport Data.Word (Word16, Word32, Word64)\nimport Domain\n\ndeclare (Just (False, True)) mempty\n  =\u003c\u003c loadSchema \"schemas/model.yaml\"\n```\n\nAnd that will cause the compiler to generate the following declarations:\n\n```haskell\ndata ServiceAddress =\n  NetworkServiceAddress !NetworkAddress |\n  LocalServiceAddress !FilePath\n\ndata NetworkAddress =\n  NetworkAddress {\n    networkAddressProtocol :: !TransportProtocol,\n    networkAddressHost :: !Host,\n    networkAddressPort :: !Word16\n  }\n\ndata TransportProtocol =\n  TcpTransportProtocol |\n  UdpTransportProtocol\n\ndata Host =\n  IpHost !Ip |\n  NameHost !Text\n\ndata Ip =\n  V4Ip !Word32 |\n  V6Ip !Word128\n\ndata Word128 =\n  Word128 {\n    word128Part1 :: !Word64,\n    word128Part2 :: !Word64\n  }\n```\n\nAs you can see in the generated code the field names from the schema get translated to record fields or constructors depending on the type composition method.\n\nIn this example the record fields are prefixed with type names for disambiguation, but by modifying the options passed to the `declare` function it is possible to remove the type name prefix or prepend with underscore, you can also avoid generating record fields altogether (to keep the value-level namespace clean).\n\nThe constructor names are also disambiguated by appending the type name to the label from schema. Thus we are introducing a consistent naming convention, while avoiding the boilerplate in the declaration of the model.\n\n### Instances\n\nIf we introduce the following change to our code:\n\n```diff\n-declare (Just (False, True)) mempty\n+declare (Just (False, True)) stdDeriver\n```\n\nWe'll get a ton of instances generated including the obvious `Show`, `Eq` and even `Hashable` for all the declared types. We'll also get some useful ones, which you wouldn't derive otherwise.\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eListing of generated instances\u003c/strong\u003e (it's big)\u003c/summary\u003e\n\n```haskell\nderiving instance Show ServiceAddress\nderiving instance Eq ServiceAddress\nderiving instance Ord ServiceAddress\nderiving instance GHC.Generics.Generic ServiceAddress\nderiving instance Data.Data.Data ServiceAddress\nderiving instance base-4.14.1.0:Data.Typeable.Internal.Typeable ServiceAddress\ninstance hashable-1.3.0.0:Data.Hashable.Class.Hashable ServiceAddress\nderiving instance template-haskell-2.16.0.0:Language.Haskell.TH.Syntax.Lift ServiceAddress\ninstance GHC.Records.HasField \"network\" ServiceAddress (Maybe NetworkAddress) where\n  GHC.Records.getField (NetworkServiceAddress a) = Just a\n  GHC.Records.getField _ = Nothing\ninstance GHC.Records.HasField \"local\" ServiceAddress (Maybe FilePath) where\n  GHC.Records.getField (LocalServiceAddress a) = Just a\n  GHC.Records.getField _ = Nothing\ninstance (a ~ NetworkAddress) =\u003e\n         GHC.OverloadedLabels.IsLabel \"network\" (a -\u003e ServiceAddress) where\n  GHC.OverloadedLabels.fromLabel = NetworkServiceAddress\ninstance (a ~ FilePath) =\u003e\n         GHC.OverloadedLabels.IsLabel \"local\" (a -\u003e ServiceAddress) where\n  GHC.OverloadedLabels.fromLabel = LocalServiceAddress\ninstance (mapper ~ (NetworkAddress -\u003e NetworkAddress)) =\u003e\n         GHC.OverloadedLabels.IsLabel \"network\" (mapper\n                                                 -\u003e ServiceAddress -\u003e ServiceAddress) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ fn\n        -\u003e \\ a\n             -\u003e case a of\n                  NetworkServiceAddress a -\u003e NetworkServiceAddress (fn a)\n                  a -\u003e a\ninstance (mapper ~ (FilePath -\u003e FilePath)) =\u003e\n         GHC.OverloadedLabels.IsLabel \"local\" (mapper\n                                               -\u003e ServiceAddress -\u003e ServiceAddress) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ fn\n        -\u003e \\ a\n             -\u003e case a of\n                  LocalServiceAddress a -\u003e LocalServiceAddress (fn a)\n                  a -\u003e a\ninstance (a ~ Maybe NetworkAddress) =\u003e\n         GHC.OverloadedLabels.IsLabel \"network\" (ServiceAddress -\u003e a) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ a\n        -\u003e case a of\n             NetworkServiceAddress a -\u003e Just a\n             _ -\u003e Nothing\ninstance (a ~ Maybe FilePath) =\u003e\n         GHC.OverloadedLabels.IsLabel \"local\" (ServiceAddress -\u003e a) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ a\n        -\u003e case a of\n             LocalServiceAddress a -\u003e Just a\n             _ -\u003e Nothing\nderiving instance Show NetworkAddress\nderiving instance Eq NetworkAddress\nderiving instance Ord NetworkAddress\nderiving instance GHC.Generics.Generic NetworkAddress\nderiving instance Data.Data.Data NetworkAddress\nderiving instance base-4.14.1.0:Data.Typeable.Internal.Typeable NetworkAddress\ninstance hashable-1.3.0.0:Data.Hashable.Class.Hashable NetworkAddress\nderiving instance template-haskell-2.16.0.0:Language.Haskell.TH.Syntax.Lift NetworkAddress\ninstance GHC.Records.HasField \"protocol\" NetworkAddress TransportProtocol where\n  GHC.Records.getField (NetworkAddress a _ _) = a\ninstance GHC.Records.HasField \"host\" NetworkAddress Host where\n  GHC.Records.getField (NetworkAddress _ a _) = a\ninstance GHC.Records.HasField \"port\" NetworkAddress Word16 where\n  GHC.Records.getField (NetworkAddress _ _ a) = a\ninstance (mapper ~ (TransportProtocol -\u003e TransportProtocol)) =\u003e\n         GHC.OverloadedLabels.IsLabel \"protocol\" (mapper\n                                                  -\u003e NetworkAddress -\u003e NetworkAddress) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ fn (NetworkAddress a b c) -\u003e ((NetworkAddress (fn a)) b) c\ninstance (mapper ~ (Host -\u003e Host)) =\u003e\n         GHC.OverloadedLabels.IsLabel \"host\" (mapper\n                                              -\u003e NetworkAddress -\u003e NetworkAddress) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ fn (NetworkAddress a b c) -\u003e ((NetworkAddress a) (fn b)) c\ninstance (mapper ~ (Word16 -\u003e Word16)) =\u003e\n         GHC.OverloadedLabels.IsLabel \"port\" (mapper\n                                              -\u003e NetworkAddress -\u003e NetworkAddress) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ fn (NetworkAddress a b c) -\u003e ((NetworkAddress a) b) (fn c)\ninstance (a ~ TransportProtocol) =\u003e\n         GHC.OverloadedLabels.IsLabel \"protocol\" (NetworkAddress -\u003e a) where\n  GHC.OverloadedLabels.fromLabel = \\ (NetworkAddress a _ _) -\u003e a\ninstance (a ~ Host) =\u003e\n         GHC.OverloadedLabels.IsLabel \"host\" (NetworkAddress -\u003e a) where\n  GHC.OverloadedLabels.fromLabel = \\ (NetworkAddress _ b _) -\u003e b\ninstance (a ~ Word16) =\u003e\n         GHC.OverloadedLabels.IsLabel \"port\" (NetworkAddress -\u003e a) where\n  GHC.OverloadedLabels.fromLabel = \\ (NetworkAddress _ _ c) -\u003e c\nderiving instance Enum TransportProtocol\nderiving instance Bounded TransportProtocol\nderiving instance Show TransportProtocol\nderiving instance Eq TransportProtocol\nderiving instance Ord TransportProtocol\nderiving instance GHC.Generics.Generic TransportProtocol\nderiving instance Data.Data.Data TransportProtocol\nderiving instance base-4.14.1.0:Data.Typeable.Internal.Typeable TransportProtocol\ninstance hashable-1.3.0.0:Data.Hashable.Class.Hashable TransportProtocol\nderiving instance template-haskell-2.16.0.0:Language.Haskell.TH.Syntax.Lift TransportProtocol\ninstance GHC.Records.HasField \"tcp\" TransportProtocol Bool where\n  GHC.Records.getField TcpTransportProtocol = True\n  GHC.Records.getField _ = False\ninstance GHC.Records.HasField \"udp\" TransportProtocol Bool where\n  GHC.Records.getField UdpTransportProtocol = True\n  GHC.Records.getField _ = False\ninstance GHC.OverloadedLabels.IsLabel \"tcp\" TransportProtocol where\n  GHC.OverloadedLabels.fromLabel = TcpTransportProtocol\ninstance GHC.OverloadedLabels.IsLabel \"udp\" TransportProtocol where\n  GHC.OverloadedLabels.fromLabel = UdpTransportProtocol\ninstance (a ~ Bool) =\u003e\n         GHC.OverloadedLabels.IsLabel \"tcp\" (TransportProtocol -\u003e a) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ a\n        -\u003e case a of\n             TcpTransportProtocol -\u003e True\n             _ -\u003e False\ninstance (a ~ Bool) =\u003e\n         GHC.OverloadedLabels.IsLabel \"udp\" (TransportProtocol -\u003e a) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ a\n        -\u003e case a of\n             UdpTransportProtocol -\u003e True\n             _ -\u003e False\nderiving instance Show Host\nderiving instance Eq Host\nderiving instance Ord Host\nderiving instance GHC.Generics.Generic Host\nderiving instance Data.Data.Data Host\nderiving instance base-4.14.1.0:Data.Typeable.Internal.Typeable Host\ninstance hashable-1.3.0.0:Data.Hashable.Class.Hashable Host\nderiving instance template-haskell-2.16.0.0:Language.Haskell.TH.Syntax.Lift Host\ninstance GHC.Records.HasField \"ip\" Host (Maybe Ip) where\n  GHC.Records.getField (IpHost a) = Just a\n  GHC.Records.getField _ = Nothing\ninstance GHC.Records.HasField \"name\" Host (Maybe Text) where\n  GHC.Records.getField (NameHost a) = Just a\n  GHC.Records.getField _ = Nothing\ninstance (a ~ Ip) =\u003e\n         GHC.OverloadedLabels.IsLabel \"ip\" (a -\u003e Host) where\n  GHC.OverloadedLabels.fromLabel = IpHost\ninstance (a ~ Text) =\u003e\n         GHC.OverloadedLabels.IsLabel \"name\" (a -\u003e Host) where\n  GHC.OverloadedLabels.fromLabel = NameHost\ninstance (mapper ~ (Ip -\u003e Ip)) =\u003e\n         GHC.OverloadedLabels.IsLabel \"ip\" (mapper -\u003e Host -\u003e Host) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ fn\n        -\u003e \\ a\n             -\u003e case a of\n                  IpHost a -\u003e IpHost (fn a)\n                  a -\u003e a\ninstance (mapper ~ (Text -\u003e Text)) =\u003e\n         GHC.OverloadedLabels.IsLabel \"name\" (mapper -\u003e Host -\u003e Host) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ fn\n        -\u003e \\ a\n             -\u003e case a of\n                  NameHost a -\u003e NameHost (fn a)\n                  a -\u003e a\ninstance (a ~ Maybe Ip) =\u003e\n         GHC.OverloadedLabels.IsLabel \"ip\" (Host -\u003e a) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ a\n        -\u003e case a of\n             IpHost a -\u003e Just a\n             _ -\u003e Nothing\ninstance (a ~ Maybe Text) =\u003e\n         GHC.OverloadedLabels.IsLabel \"name\" (Host -\u003e a) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ a\n        -\u003e case a of\n             NameHost a -\u003e Just a\n             _ -\u003e Nothing\nderiving instance Show Ip\nderiving instance Eq Ip\nderiving instance Ord Ip\nderiving instance GHC.Generics.Generic Ip\nderiving instance Data.Data.Data Ip\nderiving instance base-4.14.1.0:Data.Typeable.Internal.Typeable Ip\ninstance hashable-1.3.0.0:Data.Hashable.Class.Hashable Ip\nderiving instance template-haskell-2.16.0.0:Language.Haskell.TH.Syntax.Lift Ip\ninstance GHC.Records.HasField \"v4\" Ip (Maybe Word32) where\n  GHC.Records.getField (V4Ip a) = Just a\n  GHC.Records.getField _ = Nothing\ninstance GHC.Records.HasField \"v6\" Ip (Maybe Word128) where\n  GHC.Records.getField (V6Ip a) = Just a\n  GHC.Records.getField _ = Nothing\ninstance (a ~ Word32) =\u003e\n         GHC.OverloadedLabels.IsLabel \"v4\" (a -\u003e Ip) where\n  GHC.OverloadedLabels.fromLabel = V4Ip\ninstance (a ~ Word128) =\u003e\n         GHC.OverloadedLabels.IsLabel \"v6\" (a -\u003e Ip) where\n  GHC.OverloadedLabels.fromLabel = V6Ip\ninstance (mapper ~ (Word32 -\u003e Word32)) =\u003e\n         GHC.OverloadedLabels.IsLabel \"v4\" (mapper -\u003e Ip -\u003e Ip) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ fn\n        -\u003e \\ a\n             -\u003e case a of\n                  V4Ip a -\u003e V4Ip (fn a)\n                  a -\u003e a\ninstance (mapper ~ (Word128 -\u003e Word128)) =\u003e\n         GHC.OverloadedLabels.IsLabel \"v6\" (mapper -\u003e Ip -\u003e Ip) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ fn\n        -\u003e \\ a\n             -\u003e case a of\n                  V6Ip a -\u003e V6Ip (fn a)\n                  a -\u003e a\ninstance (a ~ Maybe Word32) =\u003e\n         GHC.OverloadedLabels.IsLabel \"v4\" (Ip -\u003e a) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ a\n        -\u003e case a of\n             V4Ip a -\u003e Just a\n             _ -\u003e Nothing\ninstance (a ~ Maybe Word128) =\u003e\n         GHC.OverloadedLabels.IsLabel \"v6\" (Ip -\u003e a) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ a\n        -\u003e case a of\n             V6Ip a -\u003e Just a\n             _ -\u003e Nothing\nderiving instance Show Word128\nderiving instance Eq Word128\nderiving instance Ord Word128\nderiving instance GHC.Generics.Generic Word128\nderiving instance Data.Data.Data Word128\nderiving instance base-4.14.1.0:Data.Typeable.Internal.Typeable Word128\ninstance hashable-1.3.0.0:Data.Hashable.Class.Hashable Word128\nderiving instance template-haskell-2.16.0.0:Language.Haskell.TH.Syntax.Lift Word128\ninstance GHC.Records.HasField \"part1\" Word128 Word64 where\n  GHC.Records.getField (Word128 a _) = a\ninstance GHC.Records.HasField \"part2\" Word128 Word64 where\n  GHC.Records.getField (Word128 _ a) = a\ninstance (mapper ~ (Word64 -\u003e Word64)) =\u003e\n         GHC.OverloadedLabels.IsLabel \"part1\" (mapper\n                                               -\u003e Word128 -\u003e Word128) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ fn (Word128 a b) -\u003e (Word128 (fn a)) b\ninstance (mapper ~ (Word64 -\u003e Word64)) =\u003e\n         GHC.OverloadedLabels.IsLabel \"part2\" (mapper\n                                               -\u003e Word128 -\u003e Word128) where\n  GHC.OverloadedLabels.fromLabel\n    = \\ fn (Word128 a b) -\u003e (Word128 a) (fn b)\ninstance (a ~ Word64) =\u003e\n         GHC.OverloadedLabels.IsLabel \"part1\" (Word128 -\u003e a) where\n  GHC.OverloadedLabels.fromLabel = \\ (Word128 a _) -\u003e a\ninstance (a ~ Word64) =\u003e\n         GHC.OverloadedLabels.IsLabel \"part2\" (Word128 -\u003e a) where\n  GHC.OverloadedLabels.fromLabel = \\ (Word128 _ b) -\u003e b\n```\n\u003c/details\u003e\n\u003cp/\u003e\n\n\n### Labels\n\nAmong the generated instances you'll find instances for the `IsLabel` class. It is a class powering Haskell's `OverloadedLabels` extension. The instances we define for it let us reduce the boilerplate in the way we address our model. Here's how.\n\n#### We can access the members of records:\n\n```haskell\ngetNetworkAddressPort :: NetworkAddress -\u003e Word16\ngetNetworkAddressPort = #port\n```\n\nYep. Finally. Address your fields without crazy prefixes or dealing with disambiguation otherwise.\n\n_Labels will be unprefixed regardless of what you choose to do about record fields. You can also name them whatever you like. Literally, even `type` and `data` make up valid labels, and unless you choose to generate unprefixed record fields, you can freely use them._\n\n#### We get accessors to the members of sums as well:\n\n```haskell\ngetHostIp :: Host -\u003e Maybe Ip\ngetHostIp = #ip\n```\n\nYep. Sum types can have accessors if you look at them from a certain perspective.\n\n#### Accessors to enums - why not?\n\n```haskell\nisTransportProtocolTcp :: TransportProtocol -\u003e Bool\nisTransportProtocolTcp = #tcp\n```\n\n#### We get shortcuts to enums:\n\n```haskell\ntcpTransportProtocol :: TransportProtocol\ntcpTransportProtocol = #tcp\n```\n\n#### We can instantiate sums:\n\n```haskell\nipHost :: Ip -\u003e Host\nipHost = #ip\n```\n\n#### We can map over both record fields and sum variants:\n\n```haskell\nmapNetworkAddressHost :: (Host -\u003e Host) -\u003e NetworkAddress -\u003e NetworkAddress\nmapNetworkAddressHost = #host\n```\n\n```haskell\nmapHostIp :: (Ip -\u003e Ip) -\u003e Host -\u003e Host\nmapHostIp = #ip\n```\n\nThere's a few things worth noticing here. Unfortunately the type inferencer will be unable to automatically detect the type of the mapping lambda parameter, so it needs to have an unambiguous type. This means that often times you'll have to provide an explicit type for it. But there's a solution.\n\nThere is a [\"domain-optics\"](https://github.com/nikita-volkov/domain-optics) library which provides an integration with the [\"optics\"](https://github.com/well-typed/optics) library. By including the derivers from it in the parameters to the `declare` macro, you'll be able to map as follows without type inference issues:\n\n```haskell\nmapNetworkAddressHost :: (Host -\u003e Host) -\u003e NetworkAddress -\u003e NetworkAddress\nmapNetworkAddressHost = over #host\n```\n\nYou can read more about the \"optics\" library integration in [the Optics section](#optics).\n\n#### If we can map, then we can also set:\n\n```haskell\nsetNetworkAddressHost :: Host -\u003e NetworkAddress -\u003e NetworkAddress\nsetNetworkAddressHost host = #host (const host)\n```\n\n## Optics\n\nExtensional [\"domain-optics\"](https://github.com/nikita-volkov/domain-optics) library provides integration with [\"optics\"](https://github.com/well-typed/optics). By using the derivers from it we can get optics using labels as well.\n\nComing back to our example here's all we'll have to do to enable our model with optics:\n\n```haskell\n{-# LANGUAGE\n  TemplateHaskell,\n  StandaloneDeriving, DeriveGeneric, DeriveDataTypeable, DeriveLift,\n  FlexibleInstances, MultiParamTypeClasses,\n  DataKinds, TypeFamilies,\n  UndecidableInstances\n  #-}\nmodule Model where\n\nimport Data.Text (Text)\nimport Data.Word (Word16, Word32, Word64)\nimport Domain\nimport DomainOptics\n\ndeclare (Just (False, True)) (stdDeriver \u003c\u003e labelOpticDeriver)\n  =\u003c\u003c loadSchema \"schemas/model.yaml\"\n```\n\nHere are some of the optics that will become available to us:\n\n```haskell\nnetworkAddressHostOptic :: Lens' NetworkAddress Host\nnetworkAddressHostOptic = #host\n```\n\n```haskell\nhostIpOptic :: Prism' Host Ip\nhostIpOptic = #ip\n```\n\n```haskell\ntcpTransportProtocolOptic :: Prism' TransportProtocol ()\ntcpTransportProtocolOptic = #tcp\n```\n\n_As you may have noticed, we avoid the \"underscore-uppercase\" naming convention for prisms. With labels there's no longer any need for it._\n\nWe recommend using \"optics\" instead of direct `IsLabel` instances, because functions like `view`, `over`, `set`, `review` make your intent clearer to the reader in many cases and in some cases provide better type inference.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikita-volkov%2Fdomain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnikita-volkov%2Fdomain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikita-volkov%2Fdomain/lists"}