{"id":21450642,"url":"https://github.com/serokell/universum","last_synced_at":"2025-05-15T12:02:59.280Z","repository":{"id":12335842,"uuid":"64127077","full_name":"serokell/universum","owner":"serokell","description":":milky_way: Prelude written in @Serokell","archived":false,"fork":false,"pushed_at":"2025-01-16T17:34:12.000Z","size":914,"stargazers_count":183,"open_issues_count":38,"forks_count":28,"subscribers_count":32,"default_branch":"master","last_synced_at":"2025-05-11T08:16:06.341Z","etag":null,"topics":["alternative-prelude","base","haskell","library","prelude"],"latest_commit_sha":null,"homepage":"","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/serokell.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":"CONTRIBUTING.md","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":"2016-07-25T10:55:02.000Z","updated_at":"2025-05-01T11:07:14.000Z","dependencies_parsed_at":"2025-01-18T12:01:00.862Z","dependency_job_id":"79f23bf4-b5b5-45ab-b7b6-0c59fea45747","html_url":"https://github.com/serokell/universum","commit_stats":{"total_commits":596,"total_committers":32,"mean_commits":18.625,"dds":0.7365771812080537,"last_synced_commit":"59a5bdc7556c863e8c262612be5a9755f7dc2d1e"},"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serokell%2Funiversum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serokell%2Funiversum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serokell%2Funiversum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serokell%2Funiversum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/serokell","download_url":"https://codeload.github.com/serokell/universum/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254337612,"owners_count":22054253,"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":["alternative-prelude","base","haskell","library","prelude"],"created_at":"2024-11-23T04:16:04.920Z","updated_at":"2025-05-15T12:02:59.230Z","avatar_url":"https://github.com/serokell.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"Universum\n=========\n\n[![GitHub CI](https://github.com/serokell/universum/workflows/CI/badge.svg)](https://github.com/serokell/universum/actions)\n[![Hackage](https://img.shields.io/hackage/v/universum.svg)](https://hackage.haskell.org/package/universum)\n[![Stackage LTS](http://stackage.org/package/universum/badge/lts)](http://stackage.org/lts/package/universum)\n[![Stackage Nightly](http://stackage.org/package/universum/badge/nightly)](http://stackage.org/nightly/package/universum)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n`universum` is a custom prelude used in @Serokell that has:\n\n1. **Excellent documentation**: tutorial, migration guide from `Prelude`,\n   Haddock with examples for (almost) every function,\n   all examples are tested with [`doctest`](http://hackage.haskell.org/package/doctest),\n   documentation regarding internal module structure.\n2. `universum`-specific [HLint](http://hackage.haskell.org/package/hlint) rules:\n   [`.hlint.yaml`](https://github.com/serokell/universum/blob/master/.hlint.yaml)\n3. Focus on safety, convenience and efficiency.\n\nWhat is this file about?\n------------------------\n\nThis README contains introduction to `Universum` and a tutorial on how to use it.\n\nStructure of this tutorial\n--------------------------\n\nThis tutorial has several parts:\n\n1. [Philosophy and motivation.](#why-another-custom-prelude-)\n2. [How to use `universum`.](#how-to-use-universum-)\n3. [Changes in `Prelude` (some gotchas).](#gotchas-)\n4. [Already known things that weren't in `Prelude` brought into scope.](#things-that-you-were-already-using-but-now-you-dont-have-to-import-them-explicitly-)\n5. [New things added.](#whats-new-)\n6. [Migration guide from `Prelude`.](#migration-guide-from-prelude-)\n\nThis is neither a tutorial on _Haskell_ nor tutorial on each function contained in Universum. For detailed\ndocumentation of every function together with examples and usage, see\n[_Haddock documentation_](http://hackage.haskell.org/package/universum).\n\nWhy another custom Prelude? [↑](#structure-of-this-tutorial)\n---------------------------\n\n### Motivation\n\nAt [Serokell](https://github.com/serokell/), we strive to be as productive as possible. That's why we are using [_Haskell_](https://haskell-lang.org/). This choice of language implies\nthat we're restricted to use [`Prelude`](http://hackage.haskell.org/package/base-4.9.1.0/docs/Prelude.html):\nimplicit import of basic functions, type classes and data types. Unfortunately, the default `Prelude`\n[is considered to be not so good](https://news.ycombinator.com/item?id=8002749)\ndue to some historical reasons.\n\nThis is why we decided to use a better tool. Luckily, _Haskell_ provides us with the ability\nto replace default `Prelude` with an alternative. All we had to do is to implement a\nnew basic set of defaults. There already were plenty of [preludes](https://guide.aelve.com/haskell/alternative-preludes-zr69k1hc),\nso we didn't plan to implement everything from scratch.\nAfter some long, hot discussions, our team decided to base our custom prelude on\n[`protolude`](https://github.com/protolude/protolude).\n\nThe next section explains why we've made this choice and what we are willing to do.\nThis tutorial doesn't cover the differences from `protolude`. Instead, it explains how Universum is different from regular `Prelude`.\n\n### Main goals\n\nWhile creating and maintaining a custom prelude, we are pursuing the following goals:\n\n1. Avoid all [partial functions](https://www.reddit.com/r/haskell/comments/5n51u3/why_are_partial_functions_as_in_head_tail_bad/).\n   We like [total](http://mathworld.wolfram.com/TotalFunction.html) and exception-free functions.\n   You can still use some unsafe functions from `Universum.Unsafe` module,\n   but they are not exported by default.\n2. Use more efficient [string representations](https://www.reddit.com/r/haskell/comments/29jw0s/whats_wrong_with_string/).\n   `String` type is crushingly inefficient. All our functions either try to be polymorphic over string\n   type or use [`Text`](http://hackage.haskell.org/package/text-1.2.2.1/docs/Data-Text.html)\n   as the default string type. Because the community is evolving slowly, some libraries still use `String` type, so `String` type alias is still reexported. We recommend to avoid `String` as much as you can!\n3. Try to not reinvent the wheel. We're not trying to rebuild whole type hierarchy from scratch,\n   as it's done in [`classy-prelude`](https://github.com/snoyberg/mono-traversable).\n   Instead, we reexport common and well-known things from `base` and some other\n   libraries that are used in everyday production programming in _Haskell_.\n   \u003e **Note**: well, we did end up inventing _some_ new things.\n4. Export more useful and commonly used functions.\n   Things like `liftIO`, `ReaderT` type, `MVar`-related functions have unambiguous names,\n   are used in almost every non-trivial project, and it's really tedious to import them\n   manually every time.\n5. Make changes only when there are enough good reasons to make these changes.\n   We have a [code modification policy](CONTRIBUTING.md#code-modification-policy) which semi-formally describes pre-conditions for different types of changes.\n\nUnlike `protolude`, we are:\n\n1. Not trying to be as general as possible (thus we don't export much from\n   [`GHC.Generics`](https://github.com/sdiehl/protolude/blob/41710698eedc66fb0bfc5623d3c3a672421fbab5/src/Protolude.hs#L365)).\n2. Not trying to maintain every version of `ghc` compiler (but [at least the latest 3](/.github/workflows/ci.yml)).\n3. Trying to make writing production code easier (see\n   [enhancements and fixes](https://github.com/serokell/universum/issues)).\n\nHow to use Universum [↑](#structure-of-this-tutorial)\n--------------------\n\nOkay, enough philosophy. If you want to just start using `universum` and\nexplore it with the help of compiler, set everything up according to the instructions below.\n\nDisable the built-in prelude at the top of your file:\n\n```haskell\n{-# LANGUAGE NoImplicitPrelude #-}\n```\n\nOr directly in your project `.cabal` file, if you want to use in every module by default:\n\n```haskell\ndefault-extensions: NoImplicitPrelude\n```\n\nThen add the following import to your modules:\n\n```haskell\nimport Universum\n```\n\nIf you're using [Emacs](https://www.gnu.org/software/emacs/) and don't want to\ntype `import Universum` manually every time, you can\n[modify your configs](https://github.com/serokell/universum/issues/8#issuecomment-276444879)\na little bit.\n\nIf you want to get familiar with `universum` internal structure, you can just\nread top-level documentation for\n[`Universum`](http://hackage.haskell.org/package/universum/docs/Universum.html)\nmodule.\n\nGotchas [↑](#structure-of-this-tutorial)\n-------\n\n* `head`, `tail`, `last`, `init`, `foldl1`, `minimum` and other were-partial functions work with `NonEmpty a` instead of `[a]`.\n* Safe analogue for `head`, `foldl1`, `foldr1`, `minimum`, `maximum` functions, for instance: `safeHead :: [a] -\u003e Maybe a`.\n* `undefined` triggers a compiler warning, which is probably not what you want. Either use `throwIO`, `Except`, `error` or `bug`.\n* `map` is `fmap` now.\n* Multiple sorting functions are available without imports:\n  + `sortBy :: (a -\u003e a -\u003e Ordering) -\u003e [a] -\u003e [a]`: sorts list using given custom comparator.\n  + `sortWith :: Ord b =\u003e (a -\u003e b) -\u003e [a] -\u003e [a]`: sorts a list based on some property of its elements.\n  + `sortOn :: Ord b =\u003e (a -\u003e b) -\u003e [a] -\u003e [a]`: just like `sortWith`, but more time-efficient if function is calculated slowly (though less space-efficient). So you should write `sortOn length` (would sort elements by length) but `sortWith fst` (would sort list of pairs by first element).\n* Functions `sum` and `product` are strict now, which makes them more efficient.\n* If you try to do something like `putStrLn \"hi\"`, you'll get an error message if\n  `OverloadedStrings` is enabled – it happens because the compiler doesn't know what\n  type to infer for the string. Use `putTextLn` in this case.\n* Since `show` doesn't come from `Show` anymore, you can't write `Show` instances easily.\nSee [migration guide](#migration-guide-from-prelude-) for details.\n* You can't call some `Foldable` methods over `Maybe` and some other types.\n  `Foldable` generalization is useful but\n  [potentially error-prone](https://www.reddit.com/r/haskell/comments/60r9hu/proposal_suggest_explicit_type_application_for/).\n  Instead we created our own fully compatible with `Foldable`\n  [`Container` type class](https://github.com/serokell/universum/blob/b6353285859e9ed3544bddbf55d70237330ad64a/src/Universum/Container/Class.hs#L180)\n  but that restricts the usage of functions like `length` over `Maybe`, `Either`, `Identity` and tuples.\n  We're also using _GHC 8_ feature of\n  [custom compile-time errors](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type_errors.html)\n  to produce\n  [more helpful messages](https://github.com/serokell/universum/blob/54a742c10720f11c739f2d268365d723924b83a9/src/Containers.hs#L474).\n* As a consequence of previous point, some functions like `traverse_`, `forM_`, `sequenceA_`, etc.\n  are generalized over `Container` type classes.\n* `error` takes `Text`.\n* We are exporting a rewrite rule which replaces `toString . toText :: Text -\u003e Text` with `id`. Note that this changes semantics in some corner cases.\n\n\nThings that you were already using, but now you don't have to import them explicitly [↑](#structure-of-this-tutorial)\n------------------------------------------------------------------------------------\n\n### Commonly used libraries\n\nFirst of all, we reexport some generally useful modules: `Control.Applicative`,\n`Data.Traversable`, `Data.Monoid`, `Control.DeepSeq`, `Data.List`, and lots of others.\nJust remove unneeded imports after importing `Universum` (GHC should tell you which ones).\n\nThen, some commonly used types: `Map/HashMap/IntMap`, `Set/HashSet/IntSet`, `Seq`, `Text` and `ByteString`\n(as well as synonyms `LText` and `LByteString` for lazy versions).\n\n`liftIO` and `MonadIO` are exported by default. A lot of `IO` functions are generalized to `MonadIO`.\n\n`deepseq` is exported. For instance, if you want to force deep evaluation of some value (in IO),\nyou can write `evaluateNF a`. WHNF evaluation is possible with `evaluateWHNF a`.\n\nWe also reexport big chunks of these libraries: `mtl`, `stm`, `microlens`, `microlens-mtl`.\n\n[`Bifunctor`](http://hackage.haskell.org/package/base-4.9.1.0/docs/Data-Bifunctor.html)\ntype class with useful instances is exported.\n\n* `first` and `second` functions apply a function to first/second part of a tuple (for tuples).\n* `bimap` takes two functions and applies them to first and second parts respectively.\n\n### Text\n\nWe export `Text` and `LText`, and some functions work with `Text` instead of `String` –\nspecifically, IO functions (`readFile`, `putStrLn`, etc) and `show`. In fact, `show`\nis polymorphic and can produce strict or lazy `Text`, `String`, or `ByteString`.\nAlso, `toText/toLText/toString` can convert `Text|LText|String` types to `Text/LText/String`. If you want to convert to and from `ByteString` use `encodeUtf8/decodeUtf8` functions.\n\n### Debugging and `undefined`s\n\n`trace`, `traceM`, `traceShow`, etc. are available by default. GHC will warn you\nif you accidentally leave them in code, however (same for `undefined`).\n\nWe also have `data Undefined = Undefined` (which, too, comes with warnings).\n\n### Exceptions\n\nWe use [`safe-exceptions`](https://github.com/fpco/safe-exceptions)\nlibrary for exceptions handling. Don't import `Control.Exceptions`\nmodule explicitly. Instead use functionality from `safe-exceptions`\nprovided by `universum` or import `Control.Exceptions.Safe` module.\n\nWhat's new? [↑](#structure-of-this-tutorial)\n-----------\n\nFinally, we can move to part describing the new cool features we bring with `universum`.\n\n* `uncons` splits a list at the first element.\n* `ordNub` and `sortNub` are _O(n log n)_ versions of `nub` (which is quadratic)\n  and `hashNub` and `unstableNub` are almost _O(n)_ versions of `nub`.\n* `(\u0026)` – reverse application. `x \u0026 f \u0026 g` instead of `g $ f $ x` is useful sometimes.\n* `whenM`, `unlessM`, `ifM`, `guardM` are available and do what you expect\n  them to do (e.g. `whenM (doesFileExist \"foo\")`).\n* Very generalized version of `concatMapM`, too, is available and does what expected.\n* `readMaybe` and `readEither` are like `read` but total and give either\n  `Maybe` or `Either` with parse error.\n* `when(Just|Nothing|Left|Right|NotEmpty)[M][_]`\n  let you conditionally execute something. Before:\n\n  ```haskell\n  case mbX of\n      Nothing -\u003e return ()\n      Just x  -\u003e ... x ...\n  ```\n\n  After:\n\n  ```haskell\n  whenJust mbX $ \\x -\u003e\n      ... x ...\n  ```\n\n* `for_` for loops. There's also `forM_` but `for_` looks a bit nicer.\n\n  ```haskell\n  for_ [1..10] $ \\i -\u003e do\n      ...\n  ```\n\n* `andM`, `allM`, `anyM`, `orM` are monadic version of corresponding functions from `base`.\n* Type operator `$` for writing types like `Maybe $ Either String $ Maybe Int`.\n* `Each` type family. So this:\n\n  ```haskell\n  f :: Each [Show, Read] [a, b] =\u003e a -\u003e b -\u003e String\n  ```\n\n  translates into this:\n\n  ```haskell\n  f :: (Show a, Show b, Read a, Read b) =\u003e a -\u003e b -\u003e String\n  ```\n\n* `With` type operator. So this:\n\n  ```haskell\n  a :: With [Show, Read] a =\u003e a -\u003e a\n  ```\n\n  translates into this:\n\n  ```haskell\n  a :: (Show a, Read a) =\u003e a -\u003e a\n  ```\n\n* Variadic composition operator `(...)`. So you can write:\n\n  ```haskell\n  ghci\u003e (show ... (+)) 1 2\n  \"3\"\n  ghci\u003e show ... 5\n  \"5\"\n  ghci\u003e (null ... zip5) [1] [2] [3] [] [5]\n  True\n  ghci\u003e let process = map (+3) ... filter\n  ghci\u003e process even [1..5]\n  [5,7]\n  ```\n\n* Conversions between `Either` and `Maybe` like `rightToMaybe` and `maybeToLeft`\n  with clear semantic.\n* `using(Reader|State)[T]` functions as aliases for `flip run(Reader|State)[T]`.\n* [`One` type class](/src/Universum/Container/Class.hs)\n  for creating singleton containers. Even monomorhpic ones like `Text`.\n* `evaluateWHNF` and `evaluateNF` functions as clearer and lifted aliases for\n  `evaluate` and `evaluate . force`.\n* `ToPairs` type class for data types that can be converted to list of pairs (like `Map` or `HashMap` or `IntMap`).\n\nMigration guide from Prelude [↑](#structure-of-this-tutorial)\n----------------------------\n\nIn order to replace default `Prelude` with `universum` you should start with instructions given in\n[how to use universum](https://github.com/serokell/universum#how-to-use-universum-) section.\n\nThis section describes what you need to change to make your code compile with `universum`.\n\n1. Enable `-XOverloadedStrings` and `-XTypeFamilies` extension by default for your project.\n2. Since `head`, `tail`, `minimum` and some other functions work for `NonEmpty` you should\n   refactor your code in one of the multiple ways described below:\n   1. Change `[a]` to `NonEmpty a` where it makes sense.\n   2. Use functions which return `Maybe`. They can be implemented using `nonEmpty` function. Like `head \u003c$\u003e nonEmpty l`.\n       + `head \u003c$\u003e nonEmpty l` is `safeHead l`\n       + `tail` is `drop 1`. It's almost never a good idea to use `tail` from `Prelude`.\n   3. Add `import qualified Universum.Unsafe as Unsafe` and replace function with qualified usage.\n3. If you use `fromJust` or `!!` you should use them from `import qualified Universum.Unsafe as Unsafe`.\n4. Derive or implement `Container` instances for your data types which implement\n   `Foldable` instances. This can be done in a single line because `Container`\n   type class automatically derives from `Foldable`.\n5. `Container` type class from `universum` replaces `Foldable` and doesn't have\n   instances for `Maybe a`, `(a, b)`, `Identity a` and `Either a b`. If you use\n   `foldr` or `forM_` or similar for something like `Maybe a` you should replace\n   usages of such function with monomorhpic alternatives:\n   * `Maybe`\n     + `(?:)          :: Maybe a -\u003e a -\u003e a`\n     + `fromMaybe     :: a -\u003e Maybe a -\u003e a`\n     + `maybeToList   :: Maybe a -\u003e [a]`\n     + `maybeToMonoid :: Monoid m =\u003e Maybe m -\u003e m`\n     + `maybe         :: b -\u003e (a -\u003e b) -\u003e Maybe a -\u003e b`\n     + `whenJust      :: Applicative f =\u003e Maybe a -\u003e (a -\u003e f ()) -\u003e f ()`\n     + `whenJustM     :: Monad m =\u003e m (Maybe a) -\u003e (a -\u003e m ()) -\u003e m ()`\n\n   * `Either`\n     + `fromLeft   :: a -\u003e Either a b -\u003e a`\n     + `fromRight  :: b -\u003e Either a b -\u003e b`\n     + `either     :: (a -\u003e c) -\u003e (b -\u003e c) -\u003e Either a b -\u003e c`\n     + `whenRight  :: Applicative f =\u003e Either l r -\u003e (r -\u003e f ()) -\u003e f ()`\n     + `whenRightM :: Monad m =\u003e m (Either l r) -\u003e (r -\u003e m ()) -\u003e m ()`\n\n6. If you have types like `foo :: Foldable f =\u003e f a -\u003e a -\u003e a` you should chose one of the following:\n   + `Right`: Modify types for `Container` like `foo :: (Container t, Element t ~ a) =\u003e t -\u003e a -\u003e a`.\n   + `Left`: Import `Data.Foldable` module `qualified` and use everything `Foldable`-related qualified.\n7. Forget about `String` type.\n   + Replace `putStr` and `putStrLn` with `putText` and `putTextLn`.\n   + Replace `(++)` with `(\u003c\u003e)` for `String`-like types.\n   + Try to use [`fmt`](http://hackage.haskell.org/package/fmt) library if you need to construct messages.\n   + Use `toText/toLText/toString` functions to convert to `Text/LazyText/String` types.\n   + Use `encodeUtf8/decodeUtf8` to convert to/from `ByteString`.\n8. Run `hlint` using `.hlint.yaml` file from `universum` package to cleanup code and imports.\n9. Since vanilla `show` from the `Show` class is not available, your custom `Show` instances will fail to compile.\nYou can `import qualified Text.Show` to bring vanilla `show` to scope with qualified name.\nIt will not conflict with `show` from `universum` and your `Show` instances will compile successfully.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserokell%2Funiversum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fserokell%2Funiversum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserokell%2Funiversum/lists"}