{"id":17131297,"url":"https://github.com/dfithian/haskell-style-guide","last_synced_at":"2026-01-05T22:05:00.592Z","repository":{"id":85573544,"uuid":"85242932","full_name":"dfithian/haskell-style-guide","owner":"dfithian","description":null,"archived":false,"fork":false,"pushed_at":"2021-11-22T15:06:33.000Z","size":12,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-29T11:11:25.482Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dfithian.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2017-03-16T21:24:35.000Z","updated_at":"2024-03-28T21:43:52.000Z","dependencies_parsed_at":null,"dependency_job_id":"ea554e26-339c-4de7-becf-1fc7c741ffb8","html_url":"https://github.com/dfithian/haskell-style-guide","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfithian%2Fhaskell-style-guide","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfithian%2Fhaskell-style-guide/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfithian%2Fhaskell-style-guide/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfithian%2Fhaskell-style-guide/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dfithian","download_url":"https://codeload.github.com/dfithian/haskell-style-guide/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245212304,"owners_count":20578443,"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":[],"created_at":"2024-10-14T19:23:08.626Z","updated_at":"2026-01-05T22:05:00.563Z","avatar_url":"https://github.com/dfithian.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Haskell Style Guide\n\n## Introduction\n\nThis document is intended to be used as a reference for Haskell style and usage. It covers a range\nof topics, from libraries and language features to style and usage.\n\n## Build System\n\n- Stack with Hpack is build system of choice. On a per-project basis, this permits concise\n  `package.yaml` files that are generated into version-compatible `.cabal` files.\n\n### Language Features\n\n#### Compiler Options\n\n- `-Wall`, `-Werror`, and `-fwarn-tabs` for warnings\n- `-threaded` (executables only) for the threaded runtime\n- `-02` for runtime performance (recommended), though it produces slower compilation times\n- `-rtsopts`, `-with-rtsopts=-N`, and `-with-rtsopts=-T` (executables only) for enabling runtime\n  options, using all processors, and showing runtime statistics, respectively\n\n#### Pragmas\n\nPragmas to use anytime (and should be turned on by default):\n\n- `ConstraintKinds` to use constraints as type synonyms\n- `DataKinds` to use data constructors as types\n- `DefaultSignatures` to allow default implementations inside a class declaration for all or some of its methods\n- `DeriveDataTypeable` for deriving\n- `DeriveGeneric` for deriving\n- `EmptyDataDecls` for empty data declarations like `data Foo`\n- `FlexibleContexts` to relax some restrictions on the form of a type signature\n- `FlexibleInstances` to relax some restrictions on the form of a class signature\n- `FunctionalDependencies` to constrain the parameters of a multi-parameter class by stating that one of the parameters can be determined from the others\n- `GADTs` to allow generalized ADTs\n- `GeneralizedNewtypeDeriving` to derive any class through a newtype\n- `InstanceSigs` to give type signatures in instances, which can be useful documentation\n- `LambdaCase` to desugar `\\ case` to `\\ x -\u003e case x of`\n- `MultiParamTypeClasses` to have classes with multiple parameters\n- `NamedFieldPuns` to bind local names to field names of a record\n- `NoImplicitPrelude` to allow use of classy prelude\n- `NoMonomorphismRestriction` to prevent the compiler from filling in free types with defaults\n- `OverloadedStrings` to allow string literals to be interpreted as different string representations\n- `PackageImports` to allow qualified imports\n- `PatternSynonyms` to provide names to pattern matches\n- `PolyKinds` to allow declarations to have kind variables, like `data Proxy (a :: k)`\n- `QuasiQuotes` to help template haskell\n- `RankNTypes` to put all variable declarations within the same `forall`\n- `RecordWildCards` to allow wildcards in data types\n- `ScopedTypeVariables` to allow usage of explicit forall type variables to be used in the body\n- `StandaloneDeriving` to allow deriving after type declaration\n- `TemplateHaskell` to turn on template haskell\n- `TupleSections` to allow `(a,)` to desugar to `\\ b -\u003e (a, b)`\n- `TypeApplications` to provide explicit type arguments to polymorphic functions\n- `TypeFamilies` to allow type families\n- `TypeOperators` to allow types as operators\n- `ViewPatterns` to allow function application on values at the time they are unwrapped\n\nPragmas to use sparingly:\n\n- `UndecidableInstances` (see [this](https://www.reddit.com/r/haskell/comments/5zjwym/when_is_undecidableinstances_okay_to_use/) explanation for details)\n\nPragmas to probably panic about:\n\n- `OverlappingInstances`\n\n### Libraries\n\nEssential libraries and frameworks:\n\n- `aeson` for JSON parsing and serialization\n- `classy-prelude` like prelude, but way better\n- `ekg` and associated libraries for statistics\n- `lens` for high powered operations on data types\n- `opaleye` to access a database\n- `servant` for web applications\n\nMore high-powered libraries:\n- `syb` for fancy recursion\n- `vinyl` for HLists and the like\n\n## General Style\n\nIn general, adhere to a certain flavor of style for a cohesive feel:\n\n- Indentation level is 2 spaces\n- Column width is 100\n- Dealing with multiple lines:\n    - When in doubt, adhere to Stylish Haskell\n    - Multiline function calls should add an indentation level to any extra lines in order to distinguish those lines from surrounding code\n    - Multiline function definitions should align arrows (`-\u003e`)\n        - Function definition constraints may go on the same line as the function name\n        - Start a newline and add an indent level (no need to be aligned under `::`)\n    - Multiline lists should add a space after the list-opening character (`[` or `(`), align the first character of each line, and start each line\n    - with a comma\n    - Multiline expressions should have operators at the _beginning_ of each continued line, indented from the first.\n        - Exception: place `$` at the end of the line, mostly.\n    - Multiline imports should add a newline after the module name and indent (maintained by stylish-haskell)\n- No wildcard imports except for ClassyPrelude\n    - Okay in tests if you wildcard import the module you are testing\n        - When you do this, import it in a separate section and call out the fact that you are doing it only because it's a test\n        ```haskell\n        module FooSpec where\n        import ClassyPrelude\n        import LiterallyAnythingElseDontYouDareWildcard ()\n\n        -- imported wildcard because it's being tested\n        import Foo\n        ```\n- In general, no orphan instances (sometimes, sometimes they may be necessary)\n  - Okay in tests\n  - Always write these in a separate module named `\u003cClassName\u003eOrphanInstances.hs`\n- Avoid importing multiple modules qualified under the same name (e.g. `import qualified Foo as F` and `import qualified Bar as F` together is bad)\n- Record field names should be prefixed by the entire type name in camel case\n    - If lenses are generated for this type, each record name should start with an underscore\n    - An added advantage of declaring record types this way is that derived JSON instances do not have collisions\n- Avoid `\u0026` and the lens operators, e.g. `.~`.\n    - _Exception_: certain APIs lead to very unwieldy code unless the lens operators are used, specifically swagger2. In that case we use them liberally.\n- Avoid unnecessary parentheses\n    - _Exception_: instance declarations that have constraints are easier to read if any constraints (or even a single constraint) are surrounded by parens. See example below.\n\n### Examples\n\nSome examples as a guide. These aren't rules, but use best judgement to write code that is readable and easy to understand.\n\n#### `let` clauses\n\nWhen using let clauses, try to define as many clauses as possible in the same block without reusing the let keyword. For pure functions, this is enforced by the compiler, but within a do block it is up to the programmer. For example:\n\n```haskell\nfoo :: a -\u003e b -\u003e c -\u003e m d\nfoo x y z = do\n -- NOT THIS:\n let bar = baz x\n let bin = qux y\n ...\n -- this instead:\n let bar = baz x\n bin = qux y\n ...\n```\n\n#### `where` clauses\n\nWhere clauses are nice when a function can be written concisely using functions that need not be at the module level. A simple example is a fold:\n\n```haskell\nfoo :: [a] -\u003e Map b c\nfoo = foldr makeMap mempty\n where\n makeMap = ...\n```\nWhere clauses are also nice when using explicit type signatures.\n\nWhere clauses should add an indentation level to the `where` keyword, then a newline, then another indentation level, unless the function (or value) can be inlined.\n\n#### Records\n\nIf the record type will be part of the \"environment\" for any code (i.e. with MonadReader), use `makeClassy ''MyType` to generate a `HasMyType` class (as well as lenses for the fields). Otherwise, most of the time it’s useful to generate lenses with `makeLenses ''MyType`.\n\nThe fields in the declaration of a record type should be aligned:\n\n```haskell\ndata SomeReallyCoolType = SomeReallyCoolType\n  { _someReallyCoolTypeName :: Text -- ^What name is for.\n  , _someReallyCoolTypeDetailInfo :: Foo -- ^Something about detail.\n  }\nmakeLenses ``SomeReallyCoolType\n```\n\nIf you run out of space, the haddock comments can be moved to the line following each field. If so, all of them should be moved. But they need not be aligned when instantiating, because these tend to be indented and the rhs may be long:\n\n```haskell\n...\n pure $ SomeReallyCoolType\n   { _someReallyCoolTypeName = foo bar baz $ flarp \u003c$\u003e mconcat zebras\n   , _someReallyCoolTypeDetailInfo = anotherLongExpression of someValues\n   }\n```\n\n#### Being point-free\n\nBeing point-free is great until it becomes confusing. For instance, don't abuse flip in favor of writing a function point-free. Operators can also be confusing in point-free syntax, as they often need extra parentheses. Here's a bad example of a point free function:\n\n```haskell\nfoo :: a -\u003e b -\u003e c -\u003e Either Text d\nfoo x y = either ((\u003c\u003e \" messed up\") . (\"this value is \" \u003c\u003e)) id . flip (flip (bar x) y)\n```\n\nThis is better:\n\n```haskell\nfoo :: a -\u003e b -\u003e c -\u003e Either Text d\nfoo x y z = either (\\ msg -\u003e \"this value is \" \u003c\u003e msg \u003c\u003e \" messed up\") id $ bar z x y\n```\n\n#### Providing types that _could_ be inferred\n\nProvide an explicit type (or a comment!) when code is confusing. A type is preferable to a comment (if it’s equally explanatory) because a type is checked by the compiler.\n\n#### Function types and constraints\n\nIn general, stay within the line limit, indent by 2 spaces when needed, and don't be too fussy:\n\n```haskell\n-- Keep it simple:\nfoo :: MySite site =\u003e Server site Foo\n\n-- Break long lines:\nfoo :: MySite site\n  =\u003e Foo -\u003e Bar -\u003e Baz -\u003e Server site Bin\n\n-- When there many constraints, use a space inside the \"(\" to line them up, and add a line break if it helps:\ndoAThing ::\n  ( HasEnv r, HasRedisPool r, HasServerSettings r\n  , MonadLogger m, MonadReader r m, MonadUnliftIO m )\n  =\u003e Foo -\u003e Bar -\u003e Baz -\u003e m Bin\n\n-- Special case: always use parens with a constraint after \"instance\":\ninstance (HasSwagger sub) =\u003e HasSwagger (MyAuth :\u003e sub) where\n```\n\n#### Breaking up long expressions\n\n```haskell\n-- Operators on the left, and indented:\n\"A long string \"\n  \u003c\u003e tshow foo \u003c\u003e \": \"\n  \u003c\u003e tshow bar\n\n-- OK to indent creatively for better alignment:\nQ.where_ ( x Q.^. FieldFoo Q.==. Q.val foo\n     Q.\u0026\u0026. x Q.^. FieldBar Q.==. Q.val bar )\n\n-- Consider an alternative if you want things aligned:\nconcat\n  [ \"A long string \"\n  , tshow foo , \": \"\n  , ...\n\n-- $ is special:\nfooSpec = describe \"foo\" $\n  it \"is awesome\" $\n    pure True\n\n-- But you can also put it at the front if it works better\nfoo \u003c- either throwIO pure\n  . f (name \u003c\u003e \" string\") (object bar)\nmyFunction\n  $ myValue\n```\n\n## Usage\n\nWhen to use...\n\n### `newtype` vs `data` vs `type`\n\nnewtypes are great for type safety. With `GeneralizedNewtypeDeriving` turned on, (almost) any class can be derived through a newtype. The type itself is erased at runtime, so there's no performance detriment to using these. Declarations should adhere to at least one of the following rules: either the `newtype` has one record, named `unConstructorName`, or there is no record name (just a constructor), but `makePrisms` is invoked for the type.\n\n`data` is for constructors with multiple fields, ADTs, or GADTs. Always use data for types which use `deriveJSON` or similar to derive instances which depend on the field name, even if there is only one field. (This is in keeping with the rule about only naming that field `unConstructorName` for newtypes.) Best practice for using ADTs is to define a detail data type for each branch of the ADT so you get something like `data Foo = Bar BarDetail | Baz BazDetail`. This is especially useful for prisms (in the lens family).\n\ntype synonyms (aliases) are great for ascribing a specific name to a repeated union of types. With `ConstraintKinds` turned on, types can be used in class or instance declarations, like `type FooM a m = (Monad m, Foo a)` with `class FooM a m =\u003e Bar a m` where. Don't use a type alias with a simple type as in `type ImportantThing = Int`; instead use a newtype and enjoy the warm feeling of type safety.\n\n### Lenses vs accessors\n\nLenses (and prisms, for ADTs) should be generated at compile time for any type that will be a part of any significant business logic in the code. Types that are only used in an outward-facing API, for example, need not generate lenses as they are typically only used at the edge of the server.\n\n### Monad vs applicative\n\n`Applicative` provides sequential sequential application (`\u003c*\u003e`) on a value, while `Monad` provides binding (`\u003e\u003e=`). Based on the type of `(\u003e\u003e=) :: m a (a m b) m b`, we can see that `Monad` requires an `a` to be present in order to compute `m b`. But with `(\u003c*\u003e) :: f (a b) f a f b`, effects are composed/combined independently of each other. An important consequence of this is that in validation, failures can accumulate. For this reason, consider using `Applicative` when parsing or validating. Finally, it's always useful to consider using the weakest constraint when writing code, so if a block can be written using `Applicative` instead of `Monad`, it could be useful to do so.\n\n### Operator chain vs do-notation\n\nOperator chains (i.e. `\u003e\u003e=`, `\u003e\u003e`, `\u003e=\u003e`) are useful for point-free expression across a few lines. If abused, they can lead to confusing code. When in doubt, use `do` or combine operations in a `let`/`in`/`where` block.\n\n### A class\n\nClasses are useful when operator overloading is needed, also known as ad-hoc polymorphism. Useful when many types have similar operations (like a simple database insert that always takes an entity and returns the entity plus its created key).\n\nTake care when writing a class, and document any assumptions into laws to which the class should adhere. If possible, write property tests for such laws and expose them so alternative implementations can be tested.\n\n### A type family\n\nA type family is a type-level function that returns a type. Very useful when a class of functions returns the same kind of thing, and in such a case acts as a witness. A very powerful implementation of this is when it is used with functions on GADTs, so that the type family may be a witness to each GADT constructor's return type.\n\n### Constraints vs transformers\n\nConstraints are more flexible, as the only constraints that are needed for a function are the ones that are actually used. Monad transformers are much more static, as they require the valid monad stack.\n\n### ExceptT vs EitherT\n\nAlways use `ExceptT`. It is older and more widely supported.\n\n### `fail` vs `throwError` vs `error`\n\nOld rule, used in some places: In general, try not to use exceptions (`fail`). Sometimes they are unavoidable, if `MonadError` (`throwError`) is not on the stack. `MonadError` is almost always preferable, since it is recoverable. In general an exception will be considered a server error.\n\nNew rule, used in some places: when `IO` (or `MonadIO`) is in the stack, it may be more sane to use exceptions (i.e. `throwIO`) because 1) some libraries already force it on us, 2) there is a useful semantics for what happens when exceptions are thrown asynchronously, and 3) something intelligent-sounding about masking and error handling. When throwing exceptions, always create an exception type which records some meaningful information and document where exceptions can be thrown, within reason.\n\nBe aware of which rule is in force and be consistent. Consider switching to the new rule especially when doing more asynchronous IO.\n\nIt's OK to use fail only at the top level of a script or main program, for example when validating inputs.\n\nNever, ever, ever use `error`. Exception: `error \"not implemented\"` is a convenient way to create a “hole” that will allow the program to run (unlike both `_` and `undefined`, which are both rejected by the compiler when `-Werror` is enabled). These should always be cleaned up before code is deployed, and usually before it is merged.\n\n### `foldr` vs `foldl'` vs `foldl`\n\nOne can intuit some differences between these functions from the following examples:\n\n- `foldr` preserves ordering while `foldl'` reverses the list (as would `foldl`).\n```haskell\nfoldr (:) [] ['a', 'b', 'c']\n\"abc\"\n\nfoldl' (flip (:)) [] ['a', 'b', 'c']\n\"cba\"\n```\n\n- `foldl` is lazy and never finishes evaluation on an infinite list, but `foldr` does.\n```haskell\nfoldl (\\ xs x -\u003e (x+1):xs) [] [(0 :: Int)..]\n-- lazy version never exits\n\nfoldr (\\ x xs -\u003e (x+1):xs) [] [(0 :: Int)..]\n-- all the things\n```\n\n- `foldl'` uses complexity proportional to its output, while foldr uses complexity at least as proportional to its input.\n```haskell\n:set +s\nfoldl' (+) 0 [(1 :: Int)..1000000000]\n500000000500000000\n(11.43 secs, 96,000,471,976 bytes)\n\nfoldr (+) 0 [(1 :: Int)..1000000000]\n*** Exception: stack overflow\n```\n\n### `nub` vs `ordNub`\n\nYou should use `ordNub`/`ordNubBy` whenever possible. nub is an O(n^2) function that removes duplicate elements from a list. In particular, it keeps only the first occurrence of each element. `ordNub` is an O(n log n) version of the nub function that uses comparisons via `Ord` instead of `Eq`.\n\nClassyPrelude exports `ordNub`, `ordNubBy`, and `hashNub` (an O(n log n) function requiring `Hashable` and `Eq`).\n\nSee https://github.com/nh2/haskell-ordnub/blob/master/README.md#ordnub\n\n### Type applications\n\nThe language option `TypeApplications` allows a more compact syntax when it's necessary to provide a type hint to the compiler. Instead of using `::`, provide a type preceded by `@` in argument position (with no space between the `@` and the type it decorates).\n\n```haskell\nencode $ toSchema (Proxy :: Proxy Text)\n:set -XTypeApplications\nencode $ toSchema $ Proxy @Text\nencode $ toSchema $ Proxy @(Map Text Int)\n```\n\n### Pattern Synonyms\n\nTo language option `PatternSynonyms` allows naming of pattern matches.\n\nPattern synonyms are useful when you want to hide the representation of a datatype. For example, the containers package defines a type `Seq` representing finite lists. It is implemented as a special sort of tree, but the implementation is not exposed. Instead, the package defines pattern synonyms like `Empty` and `:\u003c|` which allow you to match on a `Seq` as if it were a list:\n\n```haskell\nhead :: Seq a -\u003e Maybe a\nhead Empty = Nothing\nhead (x :\u003c| _) = Just x\n```\n\n### Validation Applicatives\n\nThere are two validation libraries, `validation` and `either`.\n\n### Default QuickCheck instances\n\nNever use a default QuickCheck instance for `Text`.\n\nIt is okay to use `Test.QuickCheck.Instances.Time ()` for `UTCTime` instances, though there is a known issue around this in https://github.com/phadej/qc-instances/pull/13.\n\nIt is okay to use `Test.QuickCheck.Instances.Semigroup ()` to get `NonEmpty` instances.\n\nNever import `Test.QuickCheck.Instances ()` wholesale.\n\n## Gotchas\n\n### `MonadResource`/`ResourceT`\n\n`MonadResource` (and `MonadMask`, to some extent) operates similarly to a python context manager (aka a with statement). It's important to keep track of resources created within a `runResourceT` block because they will be cleaned up. A common example of this is `sinkCacheLength`, which can be used to read the number of bytes in a file before streaming it to S3. The behavior of `sinkCacheLength` is to \"stream the input data into a temp file and count the number of bytes present. When complete, return a new `Source` reading from the temp file together with the length of the input in bytes.\" The temp file exists for the span of the resource block. Following are two (contrived) examples illustrating improper (first) and proper (second) use of this.\n\n```haskell\nlet source = sourceLbs \"any source can go here\"\ncacheLength \u003c- runResourceT $ map (over _1 fromIntegral) $ runConduit $\n  source .| sinkCacheLength\n-- this won't work because the temp file doesn't exist out here\ndoSomethingToIt url cacheLength -- stream the thing into S3\n```\n\n### Algebraic Data Types\n\nWhen defining an ADT like `data Foo = Bar | Baz`, it's important that the data constructors `Bar` and `Baz` for the type `Foo` take at unnamed arguments. Using named arguments can result in runtime exceptions from partial functions. The REPL is helpful in illustrating this.\n\n```haskell\ndata Foo = Bar { unBar :: Int } | Baz\n:t unBar\nunBar :: Foo -\u003e Int unBar $ Bar 1\n1\nunBar $ Baz\n*** Exception: No match in record selector unBar\n```\n\nBecause of this fact, never use record syntax with ADTs.\n\nFurthermore, it's not inherently bad to declare `data Foo = Foo Int Int`, but as a matter of style it's good to give names to arguments. Instead of `data Foo = Foo Int Int` try d`ata Foo = Foo FooDetail | Bar` where `data FooDetail = FooDetail { fooDetailFirstInt :: Int, fooDetailSecondInt :: Int }`.\n\n### ADT style\n\nIn general, write sum types like this:\n- newline before =\n- Align = and | at 2 spaces of indentation\n- Docs for constructors indented 4 spaces (or if they are all short, they can all be on the same line as the constructor they pertain to.)\n- Also align `deriving` at 2 spaces of indentation\n\n```haskell\n-- |Doc for Foo\ndata Foo\n = FooBlah\n -- ^Doc for FooBlah\n | FooBleh\n -- ^Doc for FooBleh\n | FooBlorp\n -- ^Doc for FooBlorp\n deriving (Bleep, Blorp, Bloop)\n```\n\nAnd in general, write product types like this:\n- fields are prefixed by an underscore, and then the type name with the first letter lowercased. Fields are camel-cased.\n- Align `{`, `|`, and `}` at 2 spaces of indentation\n- Align the `::` (and note that `stylish-haskell` can do this for you)\n- Put deriving on the same line as `}`\n\n```haskell\n-- |Doc for FooDetail\ndata FooDetail = FooDetail\n  { _fooDetailOne :: SomeType\n  -- ^Doc for fooDetailOne\n  , _fooDetailAnotherDetal :: SomeOtherType\n  -- ^Doc for fooDetailAnotherDetail\n  } deriving (Bleep, Blorp, Bloop)\n```\n\nTypically, group such data definitions near the top of the module. After the data declarations, there should typically be a group of clauses to derive any needed instances, such as `makePrisms ''Foo` and `makeLenses FooDetail`. Typically, `makePrisms` for all sum types, and `makeLenses` for all product types. As mentioned above, use `makeClassy` instead if the product type is an \"env\" type.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdfithian%2Fhaskell-style-guide","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdfithian%2Fhaskell-style-guide","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdfithian%2Fhaskell-style-guide/lists"}