{"id":17892338,"url":"https://github.com/trskop/endo","last_synced_at":"2025-03-23T00:32:33.636Z","repository":{"id":59152192,"uuid":"28687118","full_name":"trskop/endo","owner":"trskop","description":"Endomorphism utilities.","archived":false,"fork":false,"pushed_at":"2023-06-25T14:36:12.000Z","size":110,"stargazers_count":4,"open_issues_count":1,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-12-06T19:09:10.468Z","etag":null,"topics":["endo","endomorphism","haskell","monoid"],"latest_commit_sha":null,"homepage":"","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/trskop.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog.md","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":"2015-01-01T13:35:28.000Z","updated_at":"2021-04-22T01:58:19.000Z","dependencies_parsed_at":"2024-10-28T15:38:39.068Z","dependency_job_id":null,"html_url":"https://github.com/trskop/endo","commit_stats":{"total_commits":153,"total_committers":2,"mean_commits":76.5,"dds":0.05882352941176472,"last_synced_commit":"201295b0a2efe87cce4e0bfb51bb82deadb66fae"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trskop%2Fendo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trskop%2Fendo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trskop%2Fendo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trskop%2Fendo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/trskop","download_url":"https://codeload.github.com/trskop/endo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245040235,"owners_count":20551297,"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":["endo","endomorphism","haskell","monoid"],"created_at":"2024-10-28T14:35:54.344Z","updated_at":"2025-03-23T00:32:32.865Z","avatar_url":"https://github.com/trskop.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Endo\n\n[![Hackage](http://img.shields.io/hackage/v/endo.svg)][Hackage: endo]\n[![Hackage Dependencies](https://img.shields.io/hackage-deps/v/endo.svg)](http://packdeps.haskellers.com/reverse/endo)\n[![Haskell Programming Language](https://img.shields.io/badge/language-Haskell-blue.svg)][Haskell.org]\n[![BSD3 License](http://img.shields.io/badge/license-BSD3-brightgreen.svg)][tl;dr Legal: BSD3]\n\n[![Build](https://travis-ci.org/trskop/endo.svg)](https://travis-ci.org/trskop/endo)\n\n\n## Description\n\nEndomorphism utilities.\n\n\n## Usage Examples\n\nExamples in this section were taken from real live production code, but they\nwere tamed down a little.\n\n\n### Basic Idea\n\nLets define simple application `Config` data type as:\n\n````Haskell\ndata Verbosity = Silent | Normal | Verbose | Annoying\n  deriving (Show)\n\ndata Config = Config\n    { _verbosity :: Verbosity\n    , _outputFile :: FilePath\n    }\n  deriving (Show)\n````\n\nNow lets define setters for `_verbosity` and `_outputFile`:\n\n````Haskell\nsetVerbosity :: Verbosity -\u003e E Config\nsetVerbosity b cfg = cfg{_verbosity = b}\n\nsetOutputFile :: FilePath -\u003e E Config\nsetOutputFile b cfg = cfg{_outputFile = b}\n````\n\nNote that E is defined in `Data.Monoid.Endo` module and it looks like:\n\n````Haskell\ntype E a = a -\u003e a\n````\n\nIts purpose is to simplify type signatures.\n\nNow lets get to our first example:\n\n````Haskell\nexample1 :: E Config\nexample1 = appEndo $ foldEndo\n    \u0026$ setVerbosity Annoying\n    \u0026$ setOutputFile \"an.out.put\"\n````\n\nAbove example shows us that it is possible to modify `Config` as if it was a\nmonoid, but without actually having to state it as such. In practice it is not\nalways possible to define it as `Monoid`, or at least as a `Semigroup`.\nEndomorphism are monoids under composition, therefore they are what usually\nworks in situations when the modified data type can not be instantiated as a\nmonoid.\n\n\n### Working With Corner Cases\n\nIn real applications corner cases arise quite easily, e.g. `FilePath` has one\npathological case, and that is `\"\"`. There is a lot of ways to handle it. Here\nwe will concentrate only few basic techniques to illustrate versatility of our\napproach.\n\n````Haskell\n-- | Trying to set output file to \\\"\\\" will result in keeping original value.\nsetOutputFile2 :: FilePath -\u003e E Config\nsetOutputFile2 \"\" = id\nsetOutputFile2 fp = setOutputFile fp\n\nexample2 :: E Config\nexample2 = appEndo $ foldEndo\n    \u0026$ setVerbosity Annoying\n    \u0026$ setOutputFile2 \"an.out.put\"\n````\n\nSame as above, but exploits `instance AnEndo a =\u003e AnEndo Maybe a`:\n\n````Haskell\nsetOutputFile3 :: FilePath -\u003e Maybe (E Config)\nsetOutputFile3 \"\" = Nothing\nsetOutputFile3 fp = Just $ setOutputFile fp\n\nexample3 :: E Config\nexample3 = appEndo $ foldEndo\n    \u0026$ setVerbosity Annoying\n    \u0026$ setOutputFile3 \"an.out.put\"\n````\nGreat thing about `Maybe` is the fact that it has `Alternative` and `MonadPlus`\ninstances. Using `guard` may simplify `setOutputFile3` in to definition like\nfollowing:\n\n````Haskell\nsetOutputFile3':: FilePath -\u003e Maybe (E Config)\nsetOutputFile3' fp = setOutputFile fp \u003c$ guard (not (null fp))\n````\n\nFollowing example uses common pattern of using `Either` as error reporting\nmonad. This approach can be easily modified for arbitrary error reporting\nmonad.\n\n````Haskell\nsetOutputFile4 :: FilePath -\u003e Either String (E Config)\nsetOutputFile4 \"\" = Left \"Output file: Empty file path.\"\nsetOutputFile4 fp = Right $ setOutputFile fp\n\nexample4 :: Either String (E Config)\nexample4 = appEndo \u003c\u0026$\u003e foldEndo\n    \u003c*\u003e pure (setVerbosity Annoying)\n    \u003c*\u003e setOutputFile4 \"an.out.put\"\n````\n\nNotice, that above example uses applicative style. Normally when using this\nstyle, for setting record values, one needs to keep in sync order of\nconstructor arguments and order of operations. Using `foldEndo` (and its\ndual `dualFoldEndo`) doesn't have this restriction.\n\n### Lenses\n\nInstead of setter functions one may want to use lenses. In this example we use\ntypes from [lens package][Hackage: lens], but definitions use function from\n[between package][Hackage: between]:\n\n````Haskell\nverbosity :: Lens' Config Verbosity\nverbosity = _verbosity ~@@^\u003e \\s b -\u003e s{_verbosity = b}\n\noutputFile :: Lens' Config FilePath\noutputFile = _outputFile ~@@^\u003e \\s b -\u003e s{_outputFile = b}\n````\n\nNow setting values of `Config` would look like:\n\n````Haskell\nexample5 :: E Config\nexample5 = appEndo $ foldEndo\n    \u0026$ verbosity  .~ Annoying\n    \u0026$ outputFile .~ \"an.out.put\"\n````\n\n### Other Usage\n\nProbably one of the most interesting things that can be done with this\nmodule is following:\n\n````Haskell\ninstance AnEndo Verbosity where\n    type EndoOperatesOn Verbosity = Config\n    anEndo = Endo . set verbosity\n\nnewtype OutputFile = OutputFile FilePath\n\ninstance AnEndo OutputFile where\n    type EndoOperatesOn OutputFile = Config\n    anEndo (OutputFile fp) = Endo $ outputFile .~ fp\n\nexample6 :: E Config\nexample6 = appEndo $ foldEndo\n    \u0026$ Annoying\n    \u0026$ OutputFile \"an.out.put\"\n````\n\n\n### Using with optparse-applicative\n\nThis is a more complex example that defines parser for\n[optparse-applicative][Hackage: optparse-applicative] built on top of some of\nthe above definitions:\n\n````Haskell\noptions :: Parser Config\noptions = runIdentityT $ runEndo defaultConfig \u003c$\u003e options'\n  where\n    -- All this IdentityT clutter is here to avoid orphan instances.\n    options' :: IdentityT Parser (Endo Config)\n    options' = foldEndo\n        \u003c*\u003e outputOption     -- :: IdentityT Parser (Maybe (E Config))\n        \u003c*\u003e verbosityOption  -- :: IdentityT Parser (Maybe (E Config))\n        \u003c*\u003e annoyingFlag     -- :: IdentityT Parser (E Config)\n        \u003c*\u003e silentFlag       -- :: IdentityT Parser (E Config)\n        \u003c*\u003e verboseFlag      -- :: IdentityT Parser (E Config)\n\n    defaultConfig :: Config\n    defaultConfig = Config Normal \"\"\n\n-- \u003e\u003e\u003e :main -o an.out.put --annoying\n-- Config {_verbosity = Annoying, _outputFile = \"an.out.put\"}\nmain :: IO ()\nmain = execParser (info options fullDesc) \u003e\u003e= print\n````\n\nParsers for individual options and flags are wrapped in `IdentityT`, because\nthere is no following instance:\n\n````Haskell\ninstance FoldEndoArgs r =\u003e FoldEndoArgs (Parser r)\n````\n\nBut there is:\n\n````Haskell\ninstance (Applicative f, FoldEndoArgs r) =\u003e FoldEndoArgs (IdentityT f r)\n````\n\nFunctions used by the above code example:\n\n````Haskell\noutputOption :: IdentityT Parser (Maybe (E Config))\noutputOption =\n    IdentityT . optional . option (set outputFile \u003c$\u003e parseFilePath)\n    $ short 'o' \u003c\u003e long \"output\" \u003c\u003e metavar \"FILE\"\n        \u003c\u003e help \"Store output in to a FILE.\"\n  where\n    parseFilePath = eitherReader $ \\s -\u003e\n        if null s\n            then Left \"Option argument can not be empty file path.\"\n            else Right s\n\nverbosityOption :: IdentityT Parser (Maybe (E Config))\nverbosityOption =\n    IdentityT . optional . option (set verbosity \u003c$\u003e parseVerbosity)\n    $ long \"verbosity\" \u003c\u003e metavar \"LEVEL\" \u003c\u003e help \"Set verbosity to LEVEL.\"\n  where\n    verbosityToStr = map toLower . Data.showConstr . Data.toConstr\n    verbosityIntValues = [(show $ fromEnum v, v) | v \u003c- [Silent .. Annoying]]\n    verbosityStrValues =\n        (\"default\", Normal) : [(verbosityToStr v, v) | v \u003c- [Silent .. Annoying]]\n\n    parseVerbosityError = unwords\n        [ \"Verbosity can be only number from interval\"\n        , show $ map fromEnum [minBound, maxBound :: Verbosity]\n        , \"or one of the following:\"\n        , concat . intersperse \", \" $ map fst verbosityStrValues\n        ]\n\n    parseVerbosity = eitherReader $ \\s -\u003e\n        case lookup s $ verbosityIntValues ++ verbosityStrValues of\n            Just v  -\u003e Right v\n            Nothing -\u003e Left parseVerbosityError\n\nannoyingFlag :: IdentityT Parser (E Config)\nannoyingFlag = IdentityT . flag id (verbosity .~ Annoying)\n    $ long \"annoying\" \u003c\u003e help \"Set verbosity to maximum.\"\n\nsilentFlag :: IdentityT Parser (E Config)\nsilentFlag = IdentityT . flag id (verbosity .~ Silent)\n    $ short 's' \u003c\u003e long \"silent\" \u003c\u003e help \"Set verbosity to minimum.\"\n\nverboseFlag :: IdentityT Parser (E Config)\nverboseFlag = IdentityT . flag id (verbosity .~ Verbose)\n    $ short 'v' \u003c\u003e long \"verbose\" \u003c\u003e help \"Be verbose.\"\n````\n\n\n## Building Options\n\n* `-fpedantic` (disabled by default)\n\n  Pass additional warning flags to GHC.\n\n\nLicense\n-------\n\nThe BSD 3-Clause License, see [LICENSE][] file for details.\n\n\n## Contributions\n\nContributions, pull requests and bug reports are welcome! Please don't be\nafraid to contact author using GitHub or by e-mail.\n\n\n\n[Hackage: between]:\n  http://hackage.haskell.org/package/between\n  \"between package on Hackage\"\n[Hackage: endo]:\n  http://hackage.haskell.org/package/endo\n  \"endo package on Hackage\"\n[Hackage: lens]:\n  http://hackage.haskell.org/package/lens\n  \"lens package on Hackage\"\n[Hackage: optparse-applicative]:\n  http://hackage.haskell.org/package/optparse-applicative\n  \"optparse-applicative package on Hackage\"\n[Haskell.org]:\n  http://www.haskell.org\n  \"The Haskell Programming Language\"\n[LICENSE]:\n  https://github.com/trskop/endo/blob/master/LICENSE\n  \"License of endo package.\"\n[tl;dr Legal: BSD3]:\n  https://tldrlegal.com/license/bsd-3-clause-license-%28revised%29\n  \"BSD 3-Clause License (Revised)\"\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrskop%2Fendo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftrskop%2Fendo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrskop%2Fendo/lists"}