diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,10 +1,246 @@
+1.8.3.1
+=======
+
+* [#304](https://github.com/serokell/universum/pull/304)
+  * Add support for GHC-9.14 without any user-visible changes.
+
+1.8.3
+=======
+
+* [#301](https://github.com/serokell/universum/pull/301)
+  * Relax the upper bound on `text`.
+* [#300](https://github.com/serokell/universum/pull/300)
+  * Fix building with microlens-0.5.
+
+1.8.2.2
+=======
+
+* [#297](https://github.com/serokell/universum/pull/297)
+  * Add support for GHC-9.10 without any user-visible changes.
+
+1.8.2.1
+=======
+
+* [#293](https://github.com/serokell/universum/pull/293)
+  * Add explicit kind signatures for custom operators.
+  * Bump some dependency constraints to support GHC-9.8.
+
+1.8.2
+=====
+
+* [#289](https://github.com/serokell/universum/pull/289):
+  Make universum work with LTS-21.0.
+  * Re-export `(~)` type operator.
+* [#283](https://github.com/serokell/universum/pull/283):
+  Bump the upper version bound on `text` to `2.0.2`.
+
+1.8.1.1
+=======
+
+* [#282](https://github.com/serokell/universum/pull/282):
+  Bump the upper version bound on `text` to `2.0.1`.
+
+1.8.1
+=====
+
+* [#271](https://github.com/serokell/universum/pull/271):
+  Add compatibility with tasty-hedgehog 1.2.0.0
+
+1.8.0
+=====
+
+* [#252](https://github.com/serokell/universum/pull/252):
+  Remove `Option` re-export. Use `Maybe` instead.
+
+* [#176](https://github.com/serokell/universum/issues/176):
+  Deprecate `note`.
+
+* [#206](https://github.com/serokell/universum/issues/206):
+  Remove `listToMaybe`.
+  _Migration guide:_ use `safeHead` directly with functions from
+  `Universum.Container` instead.
+
+* [#182](https://github.com/serokell/universum/issues/182):
+  Deprecate `microlens` and `microlens-mtl` dependencies.
+
+* [#165](https://github.com/serokell/universum/issues/165):
+  Change the type of `readMaybe` from `readMaybe :: Read a => String -> Maybe a`
+  to it's polymorphic version `readMaybe :: forall b a. (ToString a, Read b) => a -> Maybe b`.
+
+* [#199](https://github.com/serokell/universum/issues/199):
+  Change type of `concatMap` from `concatMap :: Foldable f => (a -> [b]) -> t a -> [b]`
+  to `concatMap :: Container c => (Element c -> [b]) -> c -> [b]`.
+
+* [250](https://github.com/serokell/universum/issues):
+  Replace `group` export from `Data.List` with `group`, `groupBy`, `groupWith` and `groupAllWith` from `Data.List.NonEmpty`.
+
+1.7.3
+=====
+
+* [#236](https://github.com/serokell/universum/issues/236):
+  Add `updateMVar'` and `updateTVar'`.
+* [#244](https://github.com/serokell/universum/issues/244)
+  Add `ToPairs` instances for `[(k, v)]` and `NonEmpty (k, v)`.
+
+* [#238](https://github.com/serokell/universum/pull/238):
+  Add `fromList`.
+
+1.7.2 (rev1)
+=====
+
+* Permit `text-1.2.5.0`.
+
+1.7.2
+=====
+
+* Permit `text-1.2.4.1`.
+* [#233](https://github.com/serokell/universum/issues/223):
+  Add `someNE`.
+
+1.7.1
+=====
+
+* [#230](https://github.com/serokell/universum/issues/230):
+  Add `hoistMaybe` and `hoistEither` functions
+  similar to [relude](https://hackage.haskell.org/package/relude-0.7.0.0/docs/Relude-Monad-Trans.html#v:hoistMaybe)
+
+1.7.0
+=====
+
+* [#221](https://github.com/serokell/universum/issues/221):
+  Add safe versions of `minimum`, `maximum`, `minimumBy`, `maximumBy`, `foldr1`, `foldl1` functions for `NonEmpty` list.
+  Old their versions from `Container` typeclass now return `Maybe` and have
+  `safe` prefix in name (e.g. `safeMinimum`).
+  Add unsafe versions of those functions to `Unsafe` module.
+* [#185](https://github.com/serokell/universum/issues/185):
+  Enable more warnings, fix all warnings.
+
+1.6.1
+=====
+
+* [#219](https://github.com/serokell/universum/issues/219):
+  Bump upper bound on `text`.
+
+1.6.0
+=====
+
+* [#207](https://github.com/serokell/universum/pull/207):
+  Remove various monad transformer combinators, `flipfoldl'`, and `<<$>>`
+  from the list of changes suggested in `.hlint.yaml`.
+* [#214](https://github.com/serokell/universum/issues/214):
+  Update supported GHC versions (replace 7.10.3 with 8.6.5).
+
+* [#212](https://github.com/serokell/universum/issues/212)
+  Added rewrite rule for `toString . toText` case.
+  This may change semantics in some corner cases
+  (because `toString . toText` is not strictly the identity function).
+
+* [#215](https://github.com/serokell/universum/pull/215):
+  Fix docstrings in `Universum.Lifted.File` to mention correct module when
+  referencing related functions.
+
+1.5.0
+=====
+
+* Make `error`'s stacktrace exclude site of the `error` function itself.
+
+* [#200](https://github.com/serokell/universum/pull/200):
+  Implemented a lifted version of `withFile` and added `hClose` to
+  `Universum.Lifted.File` as discussed previously in
+  [#186](https://github.com/serokell/universum/issues/186).
+
+* [#204](https://github.com/serokell/universum/issues/186):
+  Make `trace` non-polymorphic over text argument, add `traceIdWith` and `traceShowIdWith`.
+
+* [#197](https://github.com/serokell/universum/pull/197) `hPutStr`, `hPutStrLn`
+  and `hPrint` added to `Universum.Print`. The interface for the backing
+  typeclass `Universum.Print.Print` changed. It was also moved to the internal
+  module `Universum.Print.Internal` and should be considered unstable.
+
+  _Migration guide:_ The interface for the `Print` class should be considered
+  internal and may be subject to sudden change. If you **must** implement your
+  own instances, then import `Universum.Print.Internal` (be aware that there are
+  name clashes in the functions from `Universum.Print` and
+  `Universum.Print.Internal`)
+
+* [#201](https://github.com/serokell/universum/pull/201) Generalized the type of
+  `Universum.Lifted.Env.die`. Should not break existing code, apart from,
+  perhaps, type inference.
+
+1.4.0
+=====
+
+* [#167](https://github.com/serokell/universum/issues/164):
+  `identity` has been removed.
+
+  _Migration guide:_ use `Universum.id` instead.
+* [#177](https://github.com/serokell/universum/issues/177):
+  The `mask_` reexport from `safe-exceptions` has been removed.
+
+  _Migration guide:_ use `Control.Exception.Safe.mask_` from `safe-exceptions`
+  instead.
+* [#178](https://github.com/serokell/universum/issues/178):
+  `getArgs` has been removed.
+
+  _Migration guide:_ use `liftIO` directly with `System.Environment.getArgs`
+  from base.
+* [#179](https://github.com/serokell/universum/issues/179):
+  `getContents` and `interact` have been removed.
+
+  _Migration guide:_ use `liftIO` directly with `Data.Text.Lazy.IO.getContents`
+  and `Data.Text.Lazy.IO.interact`, both from the `text` package.
+* [#180](https://github.com/serokell/universum/issues/180):
+  The `Lifted.ST` module has been removed.
+
+  _Migration guide:_ use `liftIO` directly with functions from
+  `Control.Monad.ST` instead.
+* [#181](https://github.com/serokell/universum/issues/181):
+  `list` has been removed.
+
+1.3.0
+=====
+
+* [#167](https://github.com/serokell/universum/issues/164):
+  `identity` has been deprecated.
+
+  _Migration guide:_ use `Universum.id` instead.
+* [#170](https://github.com/serokell/universum/pull/170):
+  Remove `ElementConstraint` from the `Container` class.
+
+  _Migration guide:_ remove `ElementConstraint` from every instance and every type signature.
+* [#174](https://github.com/serokell/universum/issues/174)
+  The `type-operators` dependency has been removed.
+* [#177](https://github.com/serokell/universum/issues/177):
+  The `mask_` reexport from `safe-exceptions` has been deprecated.
+
+  _Migration_guide:_ use `Control.Exception.Safe.mask_` from `safe-exceptions`
+  instead.
+* [#178](https://github.com/serokell/universum/issues/178):
+  `getArgs` has been deprecated. To be removed in a future version.
+
+  _Migration guide:_ use `liftIO` directly with `System.Environment.getArgs`
+  from `base`.
+* [#179](https://github.com/serokell/universum/issues/179):
+  `getContents` and `interact` have been deprecated.
+
+  _Migration guide:_ use `liftIO` directly with `Data.Text.Lazy.IO.getContents`
+  and `Data.Text.Lazy.IO.interact`, both from the `text` package.
+* [#180](https://github.com/serokell/universum/issues/180):
+  The `Lifted.ST` module has been deprecated. To be removed in a future
+  version.
+
+  _Migration guide:_ use `liftIO` directly with functions from
+  `Control.Monad.ST` instead.
+* [#181](https://github.com/serokell/universum/issues/181):
+  `list` has been deprecated. To be removed in a future version.
+
 1.2.0
 =====
 
 * [#159](https://github.com/serokell/universum/issues/159) **Breaking change**:
   Remove `text-format` dependency.
-  
-  _Migration guide:_ import `Buildable` type class either from `text-format` or `formatting` or `fmt` library. Instead of `pretty` you can use [`fmt`](https://hackage.haskell.org/package/fmt-0.6/docs/Fmt.html#v:fmt) function.
+
+  _Migration guide:_ import `Buildable` type class either from `text-format` or `formatting` or `fmt` library. There is no direct replacement for `pretty` and `prettyL` in popular libraries. You can define `prettyL = Data.Text.Lazy.Builder.toLazyText . build` and `pretty = Data.Text.Lazy.toStrict` . prettyL`.
 * [#164](https://github.com/serokell/universum/issues/164):
   Don't reexport `log :: Floating a => a -> a`.
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,9 +2,76 @@
 
 ## :wave: Greetings Traveler!
 
-I'm glad you're reading this, I really appreciate the effort you're
+We are glad you're reading this, we really appreciate the effort you're
 putting in. Thank you for your help in making this library awesome! :sparkles:
 
+### Versioning policy
+The version of this library typically consists of at most four numbers (last numbers are assumed to be 0 if there are less than 4 numbers).
+The semantics is `marketing.breaking.feature.fix`:
+* `fix` is increased when set of exported definitions is not modified (all changes are not visible outside).
+* `feature` is increased when set of exported definitions is strictly augmented.
+* `breaking` is increased when breaking changes are made.
+* `marketing` is increased for releases which are important from marketing perspective.
+
+### Code modification policy
+
+**tl;dr**
+
+1. Issues with labels `type:not-code` and `type:fix` can be resolved at any time.
+2. Issues with labels `type:feature` and `type:breaking` can be resolved only after being added to a milestone.
+3. `features` releases happen at most once per two weeks.
+3. `breaking` releases happen at most once per five weeks.
+
+**Actual policy**
+
+We think that a custom prelude shouldn't change often.
+Because of that we have a semi-formal policy describing which modifications can be done to `universum` under which circumstances.
+We split all issues into four semi-formally defined categories:
+1. `not-code`.
+Everything that doesn't modify source code (except non-Haddock comments), `.cabal` files, other files considered by GHC (if they exist).
+Examples: fix a typo in README.md, fix something in CI configuration files, add a pull request template.
+2. `fix`.
+Everything that doesn't fit in `not-code`, but doesn't change set of exported definitions and their types or semantics.
+Examples: fix compatibility with a new version of a library/compiler, improve performance of some function (keeping its semantics intact).
+Note: despite the name, issues of this type do not necessary correspond to bug fixes.
+3. `feature`.
+Modifications which make set of exported definitions a superset of the old set of exported definitions.
+I. e. all existing definitions are left intact, only something new is added.
+Example: add a new function, re-export a type class, add a new instance of some type class.
+4. `breaking`.
+Basically all other changes.
+Examples: remove of a function, rename a type class, change arguments order of a function.
+
+Each issue should belong to only one category.
+If an issue involves multiple changes of different types, it should be split into smaller issues
+
+For each category we have a label on GitHub, these labels start with the `type:` prefix.
+There is also the `type:unclear` label for issues which don't have a list of specific actions to be done.
+
+Processing of an issue depends on its category. Specifically:
+1. A `not-code` issue can be resolved at any time.
+PRs can be merged whenever GitHub allows it.
+Minimal number of approvals is set in GitHub settings.
+2. Rules for the `fix` issues are the same as for `not-code` with an additional rule that PRs for such issues should increase the last component in the version and this version should be uploaded to Hackage as soon as the PR is merged (also new tag and release are created).
+3. Work on the `feature` issues is organized in milestones.
+Each milestone corresponds to a release which increases at least the third (`feature`) version component.
+Scope of each milestone is discussed in issues and is set by maintainers.
+In some cases a special issue can be created to discuss the scope of a particular milestone.
+A `feature` issue is added to a milestone if such addition is approved by at least 3 people (by explicitly stating that in GH comments).
+At least 2 of them must be from Serokell.
+However, if there are 4 people (from which at least 3 are from Serokell) who disapprove this issue, it can't be added to a milestone.
+A new `feature` release can happen only after at least 2 weeks have passed after last release (except `fix` releases).
+PR can be merged only if the corresponding `feature` issue is in the upcoming milestone.
+4. Work on the `breaking` issues is organized in almost the same way.
+However, 4 approvals (at least 3 from Serokell) are necessary for inclusion into a milestone.
+2 disapprovals from Serokell or 4 disapprovals in total are enough to prevent the issue from getting into a milestone.
+If an issue is about removal of something (`x`), it should be done in two steps.
+The first step is to deprecate `x` which happens in a `breaking` release.
+The second step is to delete `x` which happens in another `breaking` release after deprecating `x`.
+There can't be more than one breaking release in less than 5 weeks.
+
+Note: approval of any PR implies that the person who approves it confirms that the PR corresponds to the issue mentioned there and the issue has a correct type.
+
 ### How to contribute
 
 #### Report bugs or feature request
@@ -12,13 +79,18 @@
 don't hesitate to create issues
 [here](https://github.com/serokell/universum/issues/new) in free format.
 
+#### Share your opinion in existing issues and pull requests
+We want `universum` to be suitable for as many Haskell developers as possible.
+That's why we would greatly appreciate your comments in our issues and pull requests.
+Please tell us whether you think that proposed modifications makes sense and are worth doing.
+
 #### Create a PR
 We love receiving pull requests from everyone. But, please, don't create a PR
-without a corresponding issue. It's always better to discuss your future
-work first. Even if such an issue exist it's still better to express your willing
+without a corresponding issue. It's always a good idea to express your wish
 to do that issue under comment section. Thus you will show that you're doing
 that issue, and nobody else will accidentally do it in parallel with you. Furthermore you
 also can discuss the best way to implement that issue!
+Make sure to read the [Code modification policy](#code-modification-policy) before starting your work on any issue.
 
 To get started with this you should first fork, then clone the repo:
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,8 +1,7 @@
 Universum
 =========
 
-[![Build Status](https://travis-ci.org/serokell/universum.svg?branch=master)](https://travis-ci.org/serokell/universum)
-[![Windows build status](https://ci.appveyor.com/api/projects/status/github/serokell/universum?branch=master&svg=true)](https://ci.appveyor.com/project/ChShersh/universum)
+[![GitHub CI](https://github.com/serokell/universum/workflows/CI/badge.svg)](https://github.com/serokell/universum/actions)
 [![Hackage](https://img.shields.io/hackage/v/universum.svg)](https://hackage.haskell.org/package/universum)
 [![Stackage LTS](http://stackage.org/package/universum/badge/lts)](http://stackage.org/lts/package/universum)
 [![Stackage Nightly](http://stackage.org/package/universum/badge/nightly)](http://stackage.org/nightly/package/universum)
@@ -13,12 +12,10 @@
 1. **Excellent documentation**: tutorial, migration guide from `Prelude`,
    Haddock with examples for (almost) every function,
    all examples are tested with [`doctest`](http://hackage.haskell.org/package/doctest),
-   documenation regarding internal module structure.
+   documentation regarding internal module structure.
 2. `universum`-specific [HLint](http://hackage.haskell.org/package/hlint) rules:
    [`.hlint.yaml`](https://github.com/serokell/universum/blob/master/.hlint.yaml)
-3. Only a few LiquidHaskell properties right now, but LiquidHaskell is on Travis
-   CI and other properties are just waiting to be added!
-4. Focus on safety, convenience and efficiency.
+3. Focus on safety, convenience and efficiency.
 
 What is this file about?
 ------------------------
@@ -57,8 +54,7 @@
 new basic set of defaults. There already were plenty of [preludes](https://guide.aelve.com/haskell/alternative-preludes-zr69k1hc),
 so we didn't plan to implement everything from scratch.
 After some long, hot discussions, our team decided to base our custom prelude on
-[`protolude`](https://github.com/sdiehl/protolude). If you're not familiar with it,
-you can read [a tutorial about `protolude`](http://www.stephendiehl.com/posts/protolude.html).
+[`protolude`](https://github.com/protolude/protolude).
 
 The next section explains why we've made this choice and what we are willing to do.
 This tutorial doesn't cover the differences from `protolude`. Instead, it explains how Universum is different from regular `Prelude`.
@@ -80,18 +76,18 @@
    Instead, we reexport common and well-known things from `base` and some other
    libraries that are used in everyday production programming in _Haskell_.
    > **Note**: well, we did end up inventing _some_ new things.
-4. Export more useful and commonly used functions. [Hello, my name is Dmitry. I was
-   coding _Haskell_ for 3 years but still hoogling which module `liftIO` comes from.](https://twitter.com/magnars/status/834683466130345984)
+4. Export more useful and commonly used functions.
    Things like `liftIO`, `ReaderT` type, `MVar`-related functions have unambiguous names,
    are used in almost every non-trivial project, and it's really tedious to import them
    manually every time.
+5. Make changes only when there are enough good reasons to make these changes.
+   We have a [code modification policy](CONTRIBUTING.md#code-modification-policy) which semi-formally describes pre-conditions for different types of changes.
 
 Unlike `protolude`, we are:
 
 1. Not trying to be as general as possible (thus we don't export much from
    [`GHC.Generics`](https://github.com/sdiehl/protolude/blob/41710698eedc66fb0bfc5623d3c3a672421fbab5/src/Protolude.hs#L365)).
-2. Not trying to maintain every version of `ghc` compiler (only the
-   [latest 3](https://github.com/serokell/universum/blob/b6353285859e9ed3544bddbf55d70237330ad64a/.travis.yml#L15)
+2. Not trying to maintain every version of `ghc` compiler (but [at least the latest 3](/.github/workflows/ci.yml)).
 3. Trying to make writing production code easier (see
    [enhancements and fixes](https://github.com/serokell/universum/issues)).
 
@@ -132,8 +128,8 @@
 Gotchas [↑](#structure-of-this-tutorial)
 -------
 
-* `head`, `tail`, `last`, `init` work with `NonEmpty a` instead of `[a]`.
-* Safe analogue for `head` function: `safeHead :: [a] -> Maybe a`.
+* `head`, `tail`, `last`, `init`, `foldl1`, `minimum` and other were-partial functions work with `NonEmpty a` instead of `[a]`.
+* Safe analogue for `head`, `foldl1`, `foldr1`, `minimum`, `maximum` functions, for instance: `safeHead :: [a] -> Maybe a`.
 * `undefined` triggers a compiler warning, which is probably not what you want. Either use `throwIO`, `Except`, `error` or `bug`.
 * `map` is `fmap` now.
 * Multiple sorting functions are available without imports:
@@ -145,6 +141,7 @@
   `OverloadedStrings` is enabled – it happens because the compiler doesn't know what
   type to infer for the string. Use `putTextLn` in this case.
 * Since `show` doesn't come from `Show` anymore, you can't write `Show` instances easily.
+See [migration guide](#migration-guide-from-prelude-) for details.
 * You can't call some `Foldable` methods over `Maybe` and some other types.
   `Foldable` generalization is useful but
   [potentially error-prone](https://www.reddit.com/r/haskell/comments/60r9hu/proposal_suggest_explicit_type_application_for/).
@@ -152,12 +149,13 @@
   [`Container` type class](https://github.com/serokell/universum/blob/b6353285859e9ed3544bddbf55d70237330ad64a/src/Universum/Container/Class.hs#L180)
   but that restricts the usage of functions like `length` over `Maybe`, `Either`, `Identity` and tuples.
   We're also using _GHC 8_ feature of
-  [custom compile-time errors](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#custom-compile-time-errors)
+  [custom compile-time errors](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type_errors.html)
   to produce
   [more helpful messages](https://github.com/serokell/universum/blob/54a742c10720f11c739f2d268365d723924b83a9/src/Containers.hs#L474).
 * As a consequence of previous point, some functions like `traverse_`, `forM_`, `sequenceA_`, etc.
   are generalized over `Container` type classes.
 * `error` takes `Text`.
+* We are exporting a rewrite rule which replaces `toString . toText :: Text -> Text` with `id`. Note that this changes semantics in some corner cases.
 
 
 Things that you were already using, but now you don't have to import them explicitly [↑](#structure-of-this-tutorial)
@@ -286,7 +284,7 @@
 * Conversions between `Either` and `Maybe` like `rightToMaybe` and `maybeToLeft`
   with clear semantic.
 * `using(Reader|State)[T]` functions as aliases for `flip run(Reader|State)[T]`.
-* [`One` type class](https://github.com/serokell/universum/blob/master/src/Containers.hs#L473)
+* [`One` type class](/src/Universum/Container/Class.hs)
   for creating singleton containers. Even monomorhpic ones like `Text`.
 * `evaluateWHNF` and `evaluateNF` functions as clearer and lifted aliases for
   `evaluate` and `evaluate . force`.
@@ -301,7 +299,7 @@
 This section describes what you need to change to make your code compile with `universum`.
 
 1. Enable `-XOverloadedStrings` and `-XTypeFamilies` extension by default for your project.
-2. Since `head`, `tail`, `last` and `init` work for `NonEmpty` you should
+2. Since `head`, `tail`, `minimum` and some other functions work for `NonEmpty` you should
    refactor your code in one of the multiple ways described below:
    1. Change `[a]` to `NonEmpty a` where it makes sense.
    2. Use functions which return `Maybe`. They can be implemented using `nonEmpty` function. Like `head <$> nonEmpty l`.
@@ -342,12 +340,6 @@
    + Use `toText/toLText/toString` functions to convert to `Text/LazyText/String` types.
    + Use `encodeUtf8/decodeUtf8` to convert to/from `ByteString`.
 8. Run `hlint` using `.hlint.yaml` file from `universum` package to cleanup code and imports.
-
-Projects that use Universum [↑](#structure-of-this-tutorial)
----------------------------
-
-- [cardano-report-server](https://github.com/input-output-hk/cardano-report-server)
-- [cardano-sl](https://github.com/input-output-hk/cardano-sl)
-- [importify](https://github.com/serokell/importify)
-- [log-warper](https://github.com/serokell/log-warper)
-- [orgstat](https://github.com/volhovm/orgstat)
+9. Since vanilla `show` from the `Show` class is not available, your custom `Show` instances will fail to compile.
+You can `import qualified Text.Show` to bring vanilla `show` to scope with qualified name.
+It will not conflict with `show` from `universum` and your `Show` instances will compile successfully.
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/benchmark/Main.hs b/benchmark/Main.hs
--- a/benchmark/Main.hs
+++ b/benchmark/Main.hs
@@ -1,8 +1,11 @@
-module Main where
+module Main
+  ( main
+  ) where
 
 import Universum hiding (show)
 
 import Data.List (nub, zip5)
+import qualified Data.Text as T
 import Gauge (Benchmark, bench, bgroup, nf, whnf)
 import Gauge.Main (defaultMain)
 import Prelude (show)
@@ -24,6 +27,7 @@
   , bgroupConcatMap
   , bgroupMember
   , bgroupFold
+  , bgroupTextConversion
   ]
 
 bgroupList :: forall a .
@@ -68,7 +72,7 @@
       ]
 
   groupSort :: [a] -> [a]
-  groupSort = map Unsafe.head . group . sort
+  groupSort = map NonEmpty.head . group . sort
 
   safeSort :: [a] -> [a]
   safeSort = map NonEmpty.head . NonEmpty.group . sort
@@ -106,10 +110,11 @@
                    ]
   ]
  where
+  super10 :: [()] -> Bool
   super10 = null
-        ... (: []) ... Unsafe.head ... pure ... Unsafe.head
+        ... ((: []) ... Unsafe.head ... pure ... Unsafe.head
         ... (: [(), (), (), ()]) ... Unsafe.head ... (: []) ... Unsafe.head
-        ... (: [()]) ... Unsafe.head ... (: [(), ()]) ... Unsafe.head
+        ... (: [()]) ... Unsafe.head ... (: [(), ()]) ... Unsafe.head :: [()] -> [()])
 
   norm10 = null
          . (: []) . Unsafe.head . pure . Unsafe.head
@@ -171,3 +176,12 @@
     bgroup "foldl'" [ bench "flipped" $ nf flipFoldl' testList
                     , bench "base"    $ nf ghcFoldl'  testList
                     ]
+
+bgroupTextConversion :: Benchmark
+bgroupTextConversion =
+  bgroup "text conversions"
+    [ -- With @toText . toString -> id@ rewrite rules we expect ~10ns
+      -- Without the rules: >10ms
+      let txt = T.replicate 10000000 (T.singleton 'a')
+      in bench "toText . toString" $ whnf (toText . toString) txt
+    ]
diff --git a/src/Universum.hs b/src/Universum.hs
--- a/src/Universum.hs
+++ b/src/Universum.hs
@@ -1,4 +1,6 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE RankNTypes #-}
 
 {- | Main module that reexports all functionality allowed to use
 without importing any other modules. Just add next lines to your
@@ -80,8 +82,26 @@
        , module Universum.VarArg
 
          -- * Lenses
-       , module Lens.Micro
-       , module Lens.Micro.Mtl
+       , Lens
+       , Lens'
+       , Traversal
+       , Traversal'
+       , over
+       , set
+       , (%~)
+       , (.~)
+       , (^.)
+       , (^..)
+       , (^?)
+       , _1
+       , _2
+       , _3
+       , _4
+       , _5
+       , preuse
+       , preview
+       , use
+       , view
        ) where
 
 import Universum.Applicative
@@ -104,6 +124,99 @@
 import Universum.VarArg
 
 -- Lenses
-import Lens.Micro (Lens, Lens', Traversal, Traversal', over, set, (%~), (&), (.~), (<&>), (^.),
-                   (^..), (^?), _1, _2, _3, _4, _5)
-import Lens.Micro.Mtl (preuse, preview, use, view)
+import qualified Lens.Micro (ASetter, Getting, over, set, (%~), (.~), (^.),
+                             (^..), (^?), _1, _2, _3, _4, _5)
+import qualified Lens.Micro.Mtl (preuse, preview, use, view)
+#if MIN_VERSION_microlens(0,5,0)
+import Lens.Micro (Field1, Field2, Field3, Field4, Field5)
+#else
+import Lens.Micro.Internal (Field1, Field2, Field3, Field4, Field5)
+#endif
+
+{-# DEPRECATED
+    Lens
+  , Lens'
+  , Traversal
+  , Traversal'
+  , over
+  , set
+  , (%~)
+  , (.~)
+  , (^.)
+  , (^..)
+  , (^?)
+  , _1
+  , _2
+  , _3
+  , _4
+  , _5
+  , preuse
+  , preview
+  , use
+  , view
+  "Use corresponding function from 'lens' or 'microlens' package"
+#-}
+
+type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
+type Lens' s a = Lens s s a a
+
+type Traversal s t a b = forall f. Applicative f => (a -> f b) -> s -> f t
+type Traversal' s a = Traversal s s a a
+
+over :: Lens.Micro.ASetter s t a b -> (a -> b) -> s -> t
+over = Lens.Micro.over
+
+set :: Lens.Micro.ASetter s t a b -> b -> s -> t
+set = Lens.Micro.set
+
+(%~) :: Lens.Micro.ASetter s t a b -> (a -> b) -> s -> t
+(%~) = (Lens.Micro.%~)
+
+infixr 4 %~
+
+(.~) :: Lens.Micro.ASetter s t a b -> b -> s -> t
+(.~) = (Lens.Micro..~)
+
+infixr 4 .~
+
+(^.) :: s -> Lens.Micro.Getting a s a -> a
+(^.) = (Lens.Micro.^.)
+
+infixl 8 ^.
+
+(^..) :: s -> Lens.Micro.Getting (Endo [a]) s a -> [a]
+(^..) = (Lens.Micro.^..)
+
+infixl 8 ^..
+
+(^?) :: s -> Lens.Micro.Getting (First a) s a -> Maybe a
+(^?) = (Lens.Micro.^?)
+
+infixl 8 ^?
+
+_1 :: Field1 s t a b => Lens s t a b
+_1 = Lens.Micro._1
+
+_2 :: Field2 s t a b => Lens s t a b
+_2 = Lens.Micro._2
+
+_3 :: Field3 s t a b => Lens s t a b
+_3 = Lens.Micro._3
+
+_4 :: Field4 s t a b => Lens s t a b
+_4 = Lens.Micro._4
+
+_5 :: Field5 s t a b => Lens s t a b
+_5 = Lens.Micro._5
+
+preuse :: MonadState s m => Lens.Micro.Getting (First a) s a -> m (Maybe a)
+preuse = Lens.Micro.Mtl.preuse
+
+preview :: MonadReader s m => Lens.Micro.Getting (First a) s a -> m (Maybe a)
+preview = Lens.Micro.Mtl.preview
+
+use :: MonadState s m => Lens.Micro.Getting a s a -> m a
+use = Lens.Micro.Mtl.use
+
+view :: MonadReader s m => Lens.Micro.Getting a s a -> m a
+view = Lens.Micro.Mtl.view
diff --git a/src/Universum/Applicative.hs b/src/Universum/Applicative.hs
--- a/src/Universum/Applicative.hs
+++ b/src/Universum/Applicative.hs
@@ -7,10 +7,13 @@
 module Universum.Applicative
        ( module Control.Applicative
        , pass
+       , someNE
        ) where
 
 import Control.Applicative (Alternative (..), Applicative (..), Const (..), ZipList (..), liftA2,
                             liftA3, optional, (<**>))
+import Data.Functor (fmap)
+import qualified Data.List.NonEmpty as NE
 
 -- $setup
 -- >>> import Universum.Monad (Maybe)
@@ -21,6 +24,11 @@
 -- Just ()
 pass :: Applicative f => f ()
 pass = pure ()
+
+-- | Similar to 'some', but reflects in types that a non-empty list
+-- is returned.
+someNE :: Alternative f => f a -> f (NE.NonEmpty a)
+someNE x = fmap NE.fromList (some x)
 
 {-
 orAlt :: (Alternative f, Monoid a) => f a -> f a
diff --git a/src/Universum/Base.hs b/src/Universum/Base.hs
--- a/src/Universum/Base.hs
+++ b/src/Universum/Base.hs
@@ -1,9 +1,8 @@
-{-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE CPP          #-}
+{-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE Unsafe       #-}
 
--- | Reexports from @GHC.*@ modules of <https://www.stackage.org/lts-8.9/package/base-4.9.1.0 base>
--- package.
+-- | Reexports from @GHC.*@ modules of the <https://hackage.haskell.org/package/base> package.
 
 module Universum.Base
        ( -- * Base types
@@ -26,6 +25,9 @@
        , module Data.Proxy
        , module Data.Typeable
        , module Data.Void
+#if MIN_VERSION_base(4,17,0)
+       , module Data.Type.Equality
+#endif
 
        , module GHC.Base
        , module GHC.Enum
@@ -35,22 +37,14 @@
        , module GHC.Num
        , module GHC.Real
        , module GHC.Show
-
-#if MIN_VERSION_base(4,10,0)
        , module GHC.TypeNats
-#else
-       , module GHC.TypeLits
-#endif
-       , module GHC.Types
-
-#if ( __GLASGOW_HASKELL__ >= 800 )
        , module GHC.OverloadedLabels
        , module GHC.ExecutionStack
        , module GHC.Stack
 
          -- * Data.Kind
+       , Constraint
        , Type
-#endif
 
        , ($!)
        ) where
@@ -63,49 +57,36 @@
 import Numeric.Natural (Natural)
 
 -- IO
-import System.IO (FilePath, Handle, IOMode (..), stderr, stdin, stdout, withFile)
+import System.IO (FilePath, Handle, IOMode (..), stderr, stdin, stdout)
 
 -- Base typeclasses
 import Data.Eq (Eq (..))
-import Data.Foldable (Foldable, concat, concatMap, foldlM, foldrM, maximumBy, minimumBy)
+import Data.Foldable (Foldable, concat, foldlM, foldrM)
+import Data.Kind (Constraint, Type)
 import Data.Ord (Down (..), Ord (..), Ordering (..), comparing)
 import Data.Traversable (Traversable (..), fmapDefault, foldMapDefault, forM, mapAccumL, mapAccumR)
 
 -- Base GHC types
-#if ( __GLASGOW_HASKELL__ >= 710 )
 import Data.Proxy (Proxy (..))
 import Data.Typeable (Typeable)
 import Data.Void (Void, absurd, vacuous)
+#if MIN_VERSION_base(4,17,0)
+import Data.Type.Equality (type (~))
 #endif
 
-import GHC.Base (String, asTypeOf, maxInt, minInt, ord, seq)
+import GHC.Base (Bool, Char, Coercible, IO, Int, Ordering, String, Word, asTypeOf, maxInt, minInt, ord, seq)
 import GHC.Enum (Bounded (..), Enum (..), boundedEnumFrom, boundedEnumFromThen)
-import GHC.Exts (Constraint, FunPtr, Ptr)
+import GHC.ExecutionStack (getStackTrace, showStackTrace)
+import GHC.Exts (FunPtr, Ptr)
 import GHC.Float (Double (..), Float (..), Floating (acos, acosh, asin, asinh, atan, atanh, cos, cosh, exp, logBase, pi, sin, sinh, sqrt, tan, tanh, (**)))
 import GHC.Generics (Generic)
 import GHC.Num (Integer, Num (..), subtract)
+import GHC.OverloadedLabels (IsLabel (..))
 import GHC.Real hiding (showSigned, (%))
 import GHC.Show (Show)
-#if MIN_VERSION_base(4,10,0)
-import GHC.TypeNats (CmpNat, KnownNat, Nat, SomeNat (..), natVal, someNatVal)
-#else
-import GHC.TypeLits (CmpNat, KnownNat, Nat, SomeNat (..), natVal, someNatVal)
-#endif
-
-import GHC.Types (Bool, Char, Coercible, IO, Int, Ordering, Word)
-
-
-#if ( __GLASGOW_HASKELL__ >= 800 )
-import GHC.ExecutionStack (getStackTrace, showStackTrace)
-import GHC.OverloadedLabels (IsLabel (..))
 import GHC.Stack (CallStack, HasCallStack, callStack, currentCallStack, getCallStack,
                   prettyCallStack, prettySrcLoc, withFrozenCallStack)
-#endif
-
-#if ( __GLASGOW_HASKELL__ >= 800 )
--- TODO: move Constraint here later
-import Data.Kind (Type)
-#endif
+import GHC.TypeNats (CmpNat, KnownNat, Nat, SomeNat (..), natVal, someNatVal)
 
 -- $setup
 -- >>> import Universum.Function (const, ($))
@@ -117,8 +98,6 @@
 -- 3
 -- >>> const 3 $! Prelude.undefined
 -- *** Exception: Prelude.undefined
--- CallStack (from HasCallStack):
---   error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
 -- ...
 ($!) :: (a -> b) -> a -> b
 f $! x = let !vx = x in f vx
diff --git a/src/Universum/Bool/Guard.hs b/src/Universum/Bool/Guard.hs
--- a/src/Universum/Bool/Guard.hs
+++ b/src/Universum/Bool/Guard.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE Safe #-}
+
 -- | This module contains monadic predicates.
 
 module Universum.Bool.Guard
diff --git a/src/Universum/Bool/Reexport.hs b/src/Universum/Bool/Reexport.hs
--- a/src/Universum/Bool/Reexport.hs
+++ b/src/Universum/Bool/Reexport.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE Safe #-}
+
 -- | This module reexports functions to work with 'Bool' type.
 
 module Universum.Bool.Reexport
diff --git a/src/Universum/Container.hs b/src/Universum/Container.hs
--- a/src/Universum/Container.hs
+++ b/src/Universum/Container.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE Safe #-}
+
 -- | This module exports all container-related stuff.
 
 module Universum.Container
@@ -5,5 +7,5 @@
        , module Universum.Container.Reexport
        ) where
 
-import Universum.Container.Class
+import Universum.Container.Class hiding (checkingNotNull)
 import Universum.Container.Reexport
diff --git a/src/Universum/Container/Class.hs b/src/Universum/Container/Class.hs
--- a/src/Universum/Container/Class.hs
+++ b/src/Universum/Container/Class.hs
@@ -11,6 +11,7 @@
 {-# LANGUAGE UndecidableInstances    #-}
 
 {-# OPTIONS_GHC -fno-warn-unticked-promoted-constructors #-}
+{-# OPTIONS_GHC -Wno-unused-type-patterns #-}
 
 -- | Reimagined approach for 'Foldable' type hierarchy. Forbids usages
 -- of 'length' function and similar over 'Maybe' and other potentially unsafe
@@ -22,7 +23,9 @@
 module Universum.Container.Class
        ( -- * Foldable-like classes and methods
          ToPairs   (..)
+       , FromList  (..)
        , Container (..)
+       , checkingNotNull
 
        , flipfoldl'
 
@@ -36,32 +39,32 @@
        , sequenceA_
        , sequence_
        , asum
+       , concatMap
 
          -- * Others
        , One(..)
        ) where
 
 import Data.Coerce (Coercible, coerce)
-import Prelude hiding (all, and, any, elem, foldMap, foldl, foldr, mapM_, notElem, null, or, print,
-                product, sequence_, sum)
+import Data.Kind (Type)
+import Prelude hiding (all, and, any, concatMap, elem, foldMap, foldl, foldr, mapM_, notElem, null,
+#if __GLASGOW_HASKELL__ >= 910
+                foldl',
+#endif
+                or, print, product, sequence_, sum)
 
-import Universum.Applicative (Alternative (..), Const, ZipList, pass)
-import Universum.Base (Constraint, Word8)
+import Universum.Applicative (Alternative (..), Const, ZipList (..), pass)
+import Universum.Base (HasCallStack, Word8)
 import Universum.Container.Reexport (HashMap, HashSet, Hashable, IntMap, IntSet, Map, Seq, Set,
                                      Vector)
 import Universum.Functor (Identity)
-import Universum.Monad.Reexport (fromMaybe)
 import Universum.Monoid (All (..), Any (..), Dual, First (..), Last, Product, Sum)
 
-#if __GLASGOW_HASKELL__ >= 800
-import GHC.Err (errorWithoutStackTrace)
+import qualified GHC.Exts as Exts
 import GHC.TypeLits (ErrorMessage (..), Symbol, TypeError)
-#endif
 
-#if ( __GLASGOW_HASKELL__ >= 800 )
 import qualified Data.List.NonEmpty as NE
 import Universum.List.Reexport (NonEmpty)
-#endif
 
 import qualified Data.Foldable as Foldable
 
@@ -70,6 +73,8 @@
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Lazy as BSL
 
+import qualified Data.List (concatMap)
+
 import qualified Data.Text as T
 import qualified Data.Text.Lazy as TL
 
@@ -111,9 +116,9 @@
 class ToPairs t where
     {-# MINIMAL toPairs #-}
     -- | Type of keys of the mapping.
-    type Key t :: *
+    type Key t :: Type
     -- | Type of value of the mapping.
-    type Val t :: *
+    type Val t :: Type
 
     -- | Converts the structure to the list of the key-value pairs.
     -- >>> toPairs (HashMap.fromList [('a', "xxx"), ('b', "yyy")])
@@ -168,13 +173,91 @@
     elems   = M.elems
     {-# INLINE elems #-}
 
+instance ToPairs [(k, v)] where
+    type Key [(k, v)] = k
+    type Val [(k, v)] = v
+    toPairs = id
+    {-# INLINE toPairs #-}
+
+instance ToPairs (NonEmpty (k, v)) where
+    type Key (NonEmpty (k, v)) = k
+    type Val (NonEmpty (k, v)) = v
+    toPairs = NE.toList
+    {-# INLINE toPairs #-}
+
 ----------------------------------------------------------------------------
+-- FromList
+----------------------------------------------------------------------------
+
+-- | Type class for data types that can be constructed from a list.
+class FromList l where
+  type ListElement l :: Type
+  type ListElement l = Exts.Item l
+
+  type FromListC l :: Exts.Constraint
+  type FromListC l = ()
+
+  {- | Make a value from list.
+
+  For simple types like '[]' and 'Set':
+
+  @
+  'toList' . 'fromList' ≡ id
+  'fromList' . 'toList' ≡ id
+  @
+
+  For map-like types:
+
+  @
+  'toPairs' . 'fromList' ≡ id
+  'fromList' . 'toPairs' ≡ id
+  @
+
+  -}
+  fromList :: FromListC l => [ListElement l] -> l
+  default fromList
+    :: (Exts.IsList l, Exts.Item l ~ a, ListElement l ~ a)
+    => [ListElement l] -> l
+  fromList = Exts.fromList
+
+instance FromList [a]
+instance FromList (Vector a)
+instance FromList (Seq a)
+instance FromList (ZipList a) where
+    type ListElement (ZipList a) = a
+    fromList = ZipList
+instance FromList (NonEmpty a) where
+    type FromListC (NonEmpty a) = HasCallStack
+    fromList l = case l of
+      []     -> error "empty list"
+      x : xs -> x NE.:| xs
+
+instance FromList IntSet
+instance Ord a => FromList (Set a)
+instance FromList (IntMap v)
+instance Ord k => FromList (Map k v)
+#if MIN_VERSION_hashable(1,4,0)
+instance (Hashable k) => FromList (HashMap k v)
+#else
+instance (Eq k, Hashable k) => FromList (HashMap k v)
+#endif
+
+instance FromList T.Text
+instance FromList TL.Text
+instance FromList BS.ByteString where
+    type ListElement BS.ByteString = Word8
+    fromList = BS.pack
+instance FromList BSL.ByteString where
+    type ListElement BSL.ByteString = Word8
+    fromList = BSL.pack
+
+----------------------------------------------------------------------------
 -- Containers (e.g. tuples and Maybe aren't containers)
 ----------------------------------------------------------------------------
 
 -- | Default implementation of 'Element' associated type family.
-type family ElementDefault (t :: *) :: * where
-    ElementDefault (f a) = a
+type family ElementDefault (t :: Type) :: Type where
+    ElementDefault (_ a) = a
 
 -- | Very similar to 'Foldable' but also allows instances for monomorphic types
 -- like 'Text' but forbids instances for 'Maybe' and similar. This class is used as
@@ -195,14 +278,9 @@
     -- so we can't implement nice interface using old higher-kinded types
     -- approach. Implementing this as an associated type family instead of
     -- top-level family gives you more control over element types.
-    type Element t :: *
+    type Element t :: Type
     type Element t = ElementDefault t
 
-    -- | Constraint for elements. This can be used to implement more efficient
-    -- implementation of some methods. For example 'elem' for 'Set' and 'HashSet'.
-    type ElementConstraint t :: * -> Constraint
-    type ElementConstraint t = Eq
-
     -- | Convert container to list of elements.
     --
     -- >>> toList @Text "aba"
@@ -221,7 +299,7 @@
     -- >>> null @Text "aba"
     -- False
     null :: t -> Bool
-    default null :: (Foldable f, t ~ f a, Element t ~ a) => t -> Bool
+    default null :: (Foldable f, t ~ f a) => t -> Bool
     null = Foldable.null
     {-# INLINE null #-}
 
@@ -241,30 +319,19 @@
     {-# INLINE foldl' #-}
 
     length :: t -> Int
-    default length :: (Foldable f, t ~ f a, Element t ~ a) => t -> Int
+    default length :: (Foldable f, t ~ f a) => t -> Int
     length = Foldable.length
     {-# INLINE length #-}
 
-    elem :: ElementConstraint t (Element t) => Element t -> t -> Bool
+    elem :: Eq (Element t) => Element t -> t -> Bool
     default elem :: ( Foldable f
                     , t ~ f a
                     , Element t ~ a
-                    , ElementConstraint t ~ Eq
-                    , ElementConstraint t (Element t)
+                    , Eq a
                     ) => Element t -> t -> Bool
     elem = Foldable.elem
     {-# INLINE elem #-}
 
-    maximum :: Ord (Element t) => t -> Element t
-    default maximum :: (Foldable f, t ~ f a, Element t ~ a, Ord (Element t)) => t -> Element t
-    maximum = Foldable.maximum
-    {-# INLINE maximum #-}
-
-    minimum :: Ord (Element t) => t -> Element t
-    default minimum :: (Foldable f, t ~ f a, Element t ~ a, Ord (Element t)) => t -> Element t
-    minimum = Foldable.minimum
-    {-# INLINE minimum #-}
-
     foldMap :: Monoid m => (Element t -> m) -> t -> m
     foldMap f = foldr (mappend . f) mempty
     {-# INLINE foldMap #-}
@@ -278,37 +345,7 @@
       where f' k x z = k $! f x z
     {-# INLINE foldr' #-}
 
-    foldr1 :: (Element t -> Element t -> Element t) -> t -> Element t
-    foldr1 f xs =
-#if __GLASGOW_HASKELL__ >= 800
-      fromMaybe (errorWithoutStackTrace "foldr1: empty structure")
-                (foldr mf Nothing xs)
-#else
-      fromMaybe (error "foldr1: empty structure")
-                (foldr mf Nothing xs)
-#endif
-      where
-        mf x m = Just (case m of
-                           Nothing -> x
-                           Just y  -> f x y)
-    {-# INLINE foldr1 #-}
-
-    foldl1 :: (Element t -> Element t -> Element t) -> t -> Element t
-    foldl1 f xs =
-#if __GLASGOW_HASKELL__ >= 800
-      fromMaybe (errorWithoutStackTrace "foldl1: empty structure")
-                (foldl mf Nothing xs)
-#else
-      fromMaybe (error "foldl1: empty structure")
-                (foldl mf Nothing xs)
-#endif
-      where
-        mf m y = Just (case m of
-                           Nothing -> y
-                           Just x  -> f x y)
-    {-# INLINE foldl1 #-}
-
-    notElem :: ElementConstraint t (Element t) => Element t -> t -> Bool
+    notElem :: Eq (Element t) => Element t -> t -> Bool
     notElem x = not . elem x
     {-# INLINE notElem #-}
 
@@ -334,6 +371,43 @@
     safeHead = foldr (\x _ -> Just x) Nothing
     {-# INLINE safeHead #-}
 
+    safeMaximum :: Ord (Element t) => t -> Maybe (Element t)
+    default safeMaximum
+      :: (Foldable f, t ~ f a, Element t ~ a, Ord (Element t))
+      => t -> Maybe (Element t)
+    safeMaximum = checkingNotNull Foldable.maximum
+    {-# INLINE safeMaximum #-}
+
+    safeMinimum :: Ord (Element t) => t -> Maybe (Element t)
+    default safeMinimum
+      :: (Foldable f, t ~ f a, Element t ~ a, Ord (Element t))
+      => t -> Maybe (Element t)
+    safeMinimum = checkingNotNull Foldable.minimum
+    {-# INLINE safeMinimum #-}
+
+    safeFoldr1 :: (Element t -> Element t -> Element t) -> t -> Maybe (Element t)
+    safeFoldr1 f xs = foldr mf Nothing xs
+      where
+        mf x m = Just (case m of
+                           Nothing -> x
+                           Just y  -> f x y)
+    {-# INLINE safeFoldr1 #-}
+
+    safeFoldl1 :: (Element t -> Element t -> Element t) -> t -> Maybe (Element t)
+    safeFoldl1 f xs = foldl mf Nothing xs
+      where
+        mf m y = Just (case m of
+                           Nothing -> y
+                           Just x  -> f x y)
+    {-# INLINE safeFoldl1 #-}
+
+-- | Helper for lifting operations which require container to be not empty.
+checkingNotNull :: Container t => (t -> Element t) -> t -> Maybe (Element t)
+checkingNotNull f t
+  | null t = Nothing
+  | otherwise = Just $ f t
+{-# INLINE checkingNotNull #-}
+
 ----------------------------------------------------------------------------
 -- Instances for monomorphic containers
 ----------------------------------------------------------------------------
@@ -350,18 +424,18 @@
     {-# INLINE foldl #-}
     foldl' = T.foldl'
     {-# INLINE foldl' #-}
-    foldr1 = T.foldr1
-    {-# INLINE foldr1 #-}
-    foldl1 = T.foldl1
-    {-# INLINE foldl1 #-}
+    safeFoldr1 f = checkingNotNull (T.foldr1 f)
+    {-# INLINE safeFoldr1 #-}
+    safeFoldl1 f = checkingNotNull (T.foldl1 f)
+    {-# INLINE safeFoldl1 #-}
     length = T.length
     {-# INLINE length #-}
     elem c = T.isInfixOf (T.singleton c)  -- there are rewrite rules for this
     {-# INLINE elem #-}
-    maximum = T.maximum
-    {-# INLINE maximum #-}
-    minimum = T.minimum
-    {-# INLINE minimum #-}
+    safeMaximum = checkingNotNull T.maximum
+    {-# INLINE safeMaximum #-}
+    safeMinimum = checkingNotNull T.minimum
+    {-# INLINE safeMinimum #-}
     all = T.all
     {-# INLINE all #-}
     any = T.any
@@ -383,19 +457,19 @@
     {-# INLINE foldl #-}
     foldl' = TL.foldl'
     {-# INLINE foldl' #-}
-    foldr1 = TL.foldr1
-    {-# INLINE foldr1 #-}
-    foldl1 = TL.foldl1
-    {-# INLINE foldl1 #-}
+    safeFoldr1 f = checkingNotNull (TL.foldr1 f)
+    {-# INLINE safeFoldr1 #-}
+    safeFoldl1 f = checkingNotNull (TL.foldl1 f)
+    {-# INLINE safeFoldl1 #-}
     length = fromIntegral . TL.length
     {-# INLINE length #-}
     -- will be okay thanks to rewrite rules
     elem c s = TL.isInfixOf (TL.singleton c) s
     {-# INLINE elem #-}
-    maximum = TL.maximum
-    {-# INLINE maximum #-}
-    minimum = TL.minimum
-    {-# INLINE minimum #-}
+    safeMaximum = checkingNotNull TL.maximum
+    {-# INLINE safeMaximum #-}
+    safeMinimum = checkingNotNull TL.minimum
+    {-# INLINE safeMinimum #-}
     all = TL.all
     {-# INLINE all #-}
     any = TL.any
@@ -417,20 +491,20 @@
     {-# INLINE foldl #-}
     foldl' = BS.foldl'
     {-# INLINE foldl' #-}
-    foldr1 = BS.foldr1
-    {-# INLINE foldr1 #-}
-    foldl1 = BS.foldl1
-    {-# INLINE foldl1 #-}
+    safeFoldr1 f = checkingNotNull (BS.foldr1 f)
+    {-# INLINE safeFoldr1 #-}
+    safeFoldl1 f = checkingNotNull (BS.foldl1 f)
+    {-# INLINE safeFoldl1 #-}
     length = BS.length
     {-# INLINE length #-}
     elem = BS.elem
     {-# INLINE elem #-}
     notElem = BS.notElem
     {-# INLINE notElem #-}
-    maximum = BS.maximum
-    {-# INLINE maximum #-}
-    minimum = BS.minimum
-    {-# INLINE minimum #-}
+    safeMaximum = checkingNotNull BS.maximum
+    {-# INLINE safeMaximum #-}
+    safeMinimum = checkingNotNull BS.minimum
+    {-# INLINE safeMinimum #-}
     all = BS.all
     {-# INLINE all #-}
     any = BS.any
@@ -452,20 +526,20 @@
     {-# INLINE foldl #-}
     foldl' = BSL.foldl'
     {-# INLINE foldl' #-}
-    foldr1 = BSL.foldr1
-    {-# INLINE foldr1 #-}
-    foldl1 = BSL.foldl1
-    {-# INLINE foldl1 #-}
+    safeFoldr1 f = checkingNotNull (BSL.foldr1 f)
+    {-# INLINE safeFoldr1 #-}
+    safeFoldl1 f = checkingNotNull (BSL.foldl1 f)
+    {-# INLINE safeFoldl1 #-}
     length = fromIntegral . BSL.length
     {-# INLINE length #-}
     elem = BSL.elem
     {-# INLINE elem #-}
     notElem = BSL.notElem
     {-# INLINE notElem #-}
-    maximum = BSL.maximum
-    {-# INLINE maximum #-}
-    minimum = BSL.minimum
-    {-# INLINE minimum #-}
+    safeMaximum = checkingNotNull BSL.maximum
+    {-# INLINE safeMaximum #-}
+    safeMinimum = checkingNotNull BSL.minimum
+    {-# INLINE safeMinimum #-}
     all = BSL.all
     {-# INLINE all #-}
     any = BSL.any
@@ -491,10 +565,10 @@
     {-# INLINE length #-}
     elem = IS.member
     {-# INLINE elem #-}
-    maximum = IS.findMax
-    {-# INLINE maximum #-}
-    minimum = IS.findMin
-    {-# INLINE minimum #-}
+    safeMaximum = checkingNotNull IS.findMax
+    {-# INLINE safeMaximum #-}
+    safeMinimum = checkingNotNull IS.findMin
+    {-# INLINE safeMinimum #-}
     safeHead = fmap fst . IS.minView
     {-# INLINE safeHead #-}
 
@@ -502,18 +576,17 @@
 -- Efficient instances
 ----------------------------------------------------------------------------
 
-instance Container (Set v) where
-    type ElementConstraint (Set v) = Ord
+instance Ord v => Container (Set v) where
     elem = Set.member
     {-# INLINE elem #-}
     notElem = Set.notMember
     {-# INLINE notElem #-}
 
-class (Eq a, Hashable a) => CanHash a
-instance (Eq a, Hashable a) => CanHash a
-
-instance Container (HashSet v) where
-    type ElementConstraint (HashSet v) = CanHash
+#if MIN_VERSION_hashable(1,4,0)
+instance (Hashable v) => Container (HashSet v) where
+#else
+instance (Eq v, Hashable v) => Container (HashSet v) where
+#endif
     elem = HashSet.member
     {-# INLINE elem #-}
 
@@ -525,7 +598,6 @@
 instance Container [a]
 instance Container (Const a b)
 
-#if __GLASGOW_HASKELL__ >= 800
 -- Algebraic types
 instance Container (Dual a)
 instance Container (First a)
@@ -534,7 +606,6 @@
 instance Container (Sum a)
 instance Container (NonEmpty a)
 instance Container (ZipList a)
-#endif
 
 -- Containers
 instance Container (HashMap k v)
@@ -559,7 +630,6 @@
 flipfoldl' f = foldl' (flip f)
 {-# INLINE flipfoldl' #-}
 
-#if MIN_VERSION_base(4,10,1)
 -- | Stricter version of 'Prelude.sum'.
 --
 -- >>> sum [1..10]
@@ -579,11 +649,9 @@
 --           use
 --               maybeToMonoid :: Monoid m => Maybe m -> m
 -- ...
-#endif
 sum :: (Container t, Num (Element t)) => t -> Element t
 sum = foldl' (+) 0
 
-#if MIN_VERSION_base(4,10,1)
 -- | Stricter version of 'Prelude.product'.
 --
 -- >>> product [1..10]
@@ -603,7 +671,6 @@
 --           use
 --               maybeToMonoid :: Monoid m => Maybe m -> m
 -- ...
-#endif
 product :: (Container t, Num (Element t)) => t -> Element t
 product = foldl' (*) 1
 
@@ -693,11 +760,21 @@
 asum = foldr (<|>) empty
 {-# INLINE asum #-}
 
+{- | Version of 'Data.Foldable.concatMap' constrained to 'Container'.
+
+>>> concatMap (\x -> [x + 1, x + 2]) [1, 2, 3]
+[2,3,3,4,4,5]
+
+@since 1.8.0
+-}
+concatMap :: Container c => (Element c -> [b]) -> c -> [b]
+concatMap f = Data.List.concatMap f . toList
+{-# INLINE concatMap #-}
+
 ----------------------------------------------------------------------------
 -- Disallowed instances
 ----------------------------------------------------------------------------
 
-#if __GLASGOW_HASKELL__ >= 800
 type family DisallowInstance (z :: Symbol) :: ErrorMessage where
     DisallowInstance z  = Text "Do not use 'Foldable' methods on " :<>: Text z
         :$$: Text "Suggestions:"
@@ -712,20 +789,11 @@
         :$$: Text "    use"
         :$$: Text "        maybeToMonoid :: Monoid m => Maybe m -> m"
         :$$: Text ""
-#endif
 
-#if __GLASGOW_HASKELL__ >= 800
 instance TypeError (DisallowInstance "tuple")    => Container (a, b)
 instance TypeError (DisallowInstance "Maybe")    => Container (Maybe a)
 instance TypeError (DisallowInstance "Either")   => Container (Either a b)
 instance TypeError (DisallowInstance "Identity") => Container (Identity a)
-#else
-class ForbiddenFoldable a
-instance ForbiddenFoldable (a, b)       => Container (a, b)
-instance ForbiddenFoldable (Maybe a)    => Container (Maybe a)
-instance ForbiddenFoldable (Either a b) => Container (Either a b)
-instance ForbiddenFoldable (Identity a) => Container (Identity a)
-#endif
 
 ----------------------------------------------------------------------------
 -- One
@@ -753,12 +821,10 @@
     one = (:[])
     {-# INLINE one #-}
 
-#if ( __GLASGOW_HASKELL__ >= 800 )
 instance One (NE.NonEmpty a) where
     type OneItem (NE.NonEmpty a) = a
     one = (NE.:|[])
     {-# INLINE one #-}
-#endif
 
 instance One (SEQ.Seq a) where
     type OneItem (SEQ.Seq a) = a
diff --git a/src/Universum/Container/Reexport.hs b/src/Universum/Container/Reexport.hs
--- a/src/Universum/Container/Reexport.hs
+++ b/src/Universum/Container/Reexport.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE Trustworthy #-}
+
 -- | This module reexports all container related stuff from 'Prelude'.
 
 module Universum.Container.Reexport
diff --git a/src/Universum/Debug.hs b/src/Universum/Debug.hs
--- a/src/Universum/Debug.hs
+++ b/src/Universum/Debug.hs
@@ -1,16 +1,17 @@
 {-# LANGUAGE CPP                #-}
+{-# LANGUAGE DataKinds          #-}
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE DeriveGeneric      #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE ImplicitParams     #-}
 {-# LANGUAGE KindSignatures     #-}
+{-# LANGUAGE MagicHash          #-}
 {-# LANGUAGE PolyKinds          #-}
 {-# LANGUAGE RankNTypes         #-}
 {-# LANGUAGE Trustworthy        #-}
-#if ( __GLASGOW_HASKELL__ >= 804 )
-{-# LANGUAGE TypeInType         #-}
-#endif
 
 -- | Functions for debugging. If you left these functions in your code
--- then warning is generated to remind you about left usages. Also some
+-- then warning is generated to remind you about left usages. Also, some
 -- functions (and data types) are convenient for prototyping.
 
 module Universum.Debug
@@ -19,67 +20,89 @@
        , trace
        , traceM
        , traceId
+       , traceIdWith
        , traceShow
        , traceShowId
+       , traceShowIdWith
        , traceShowM
        , undefined
        ) where
 
 import Control.Monad (Monad, return)
 import Data.Data (Data)
-import Data.Text (Text, unpack)
-import Data.Typeable (Typeable)
+import Data.Text (Text, pack, unpack)
 import GHC.Generics (Generic)
 import System.IO.Unsafe (unsafePerformIO)
 
-#if ( __GLASGOW_HASKELL__ >= 800 )
-import GHC.Exts (RuntimeRep, TYPE)
-
-import Universum.Base (HasCallStack)
-#endif
+import GHC.Exception (errorCallWithCallStackException)
+import GHC.Exts (RuntimeRep, TYPE, raise#)
 
 import Universum.Applicative (pass)
-import Universum.Print (Print, putStrLn)
+import Universum.Base (HasCallStack, callStack)
+import Universum.Print (putStrLn)
 
 import qualified Prelude as P
 
--- | Generalized over string version of 'Debug.Trace.trace' that leaves warnings.
+-- | Version of 'Debug.Trace.trace' that leaves a warning and takes 'Text'.
 {-# WARNING trace "'trace' remains in code" #-}
-trace :: Print b => b -> a -> a
+trace :: Text -> a -> a
 trace string expr = unsafePerformIO (do
     putStrLn string
     return expr)
 
 -- | 'P.error' that takes 'Text' as an argument.
-#if ( __GLASGOW_HASKELL__ >= 800 )
 error :: forall (r :: RuntimeRep) . forall (a :: TYPE r) . HasCallStack
       => Text -> a
-#else
-error :: Text -> a
-#endif
-error s = P.error (unpack s)
+error s = raise# (errorCallWithCallStackException (unpack s) callStack)
 
--- | Version of 'Debug.Trace.traceShow' that leaves warning.
+-- | Version of 'Debug.Trace.traceShow' that leaves a warning.
 {-# WARNING traceShow "'traceShow' remains in code" #-}
 traceShow :: P.Show a => a -> b -> b
-traceShow a b = trace (P.show a) b
+traceShow a b = trace (pack (P.show a)) b
 
--- | Version of 'Debug.Trace.traceShow' that leaves warning.
+-- | Version of 'Debug.Trace.traceShowId' that leaves a warning.
 {-# WARNING traceShowId "'traceShowId' remains in code" #-}
 traceShowId :: P.Show a => a -> a
-traceShowId a = trace (P.show a) a
+traceShowId a = trace (pack (P.show a)) a
 
--- | Version of 'Debug.Trace.traceShowM' that leaves warning.
+{- | Version of 'Debug.Trace.traceId' that leaves a warning.
+Useful to tag printed data, for instance:
+
+@
+traceIdWith (\x -> "My data: " <> show x) (veryLargeExpression)
+@
+
+This is especially useful with custom formatters:
+
+@
+traceIdWith (\x -> "My data: " <> pretty x) (veryLargeExpression)
+@
+-}
+{-# WARNING traceIdWith "'traceIdWith' remains in code" #-}
+traceIdWith :: (a -> Text) -> a -> a
+traceIdWith f a = trace (f a) a
+
+-- | Version of 'Debug.Trace.traceShowId' that leaves a warning.
+-- Useful to tag printed data, for instance:
+--
+-- @
+-- traceShowIdWith ("My data: ", ) (veryLargeExpression)
+-- @
+{-# WARNING traceShowIdWith "'traceShowIdWith' remains in code" #-}
+traceShowIdWith :: P.Show s => (a -> s) -> a -> a
+traceShowIdWith f a = trace (pack (P.show (f a))) a
+
+-- | Version of 'Debug.Trace.traceShowM' that leaves a warning.
 {-# WARNING traceShowM "'traceShowM' remains in code" #-}
 traceShowM :: (P.Show a, Monad m) => a -> m ()
-traceShowM a = trace (P.show a) pass
+traceShowM a = trace (pack (P.show a)) pass
 
--- | Version of 'Debug.Trace.traceM' that leaves warning and takes 'Text'.
+-- | Version of 'Debug.Trace.traceM' that leaves a warning and takes 'Text'.
 {-# WARNING traceM "'traceM' remains in code" #-}
 traceM :: (Monad m) => Text -> m ()
-traceM s = trace (unpack s) pass
+traceM s = trace s pass
 
--- | Version of 'Debug.Trace.traceId' that leaves warning and takes 'Text'.
+-- | Version of 'Debug.Trace.traceId' that leaves a warning.
 {-# WARNING traceId "'traceId' remains in code" #-}
 traceId :: Text -> Text
 traceId s = trace s s
@@ -87,13 +110,9 @@
 -- | Similar to 'undefined' but data type.
 {-# WARNING Undefined "'Undefined' type remains in code" #-}
 data Undefined = Undefined
-    deriving (P.Eq, P.Ord, P.Show, P.Read, P.Enum, P.Bounded, Data, Typeable, Generic)
+    deriving stock (P.Eq, P.Ord, P.Show, P.Read, P.Enum, P.Bounded, Data, Generic)
 
--- | 'P.undefined' that leaves warning in code on every usage.
+-- | 'P.undefined' that leaves a warning in code on every usage.
 {-# WARNING undefined "'undefined' function remains in code (or use 'error')" #-}
-#if ( __GLASGOW_HASKELL__ >= 800 )
 undefined :: forall (r :: RuntimeRep) . forall (a :: TYPE r) . HasCallStack => a
-#else
-undefined :: a
-#endif
 undefined = P.undefined
diff --git a/src/Universum/DeepSeq.hs b/src/Universum/DeepSeq.hs
--- a/src/Universum/DeepSeq.hs
+++ b/src/Universum/DeepSeq.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE Trustworthy #-}
+
 -- | This module contains useful functions to evaluate expressions to weak-head
 -- normal form or just normal form. Useful to force traces or @error@ inside
 -- monadic computation or to remove space leaks.
diff --git a/src/Universum/Exception.hs b/src/Universum/Exception.hs
--- a/src/Universum/Exception.hs
+++ b/src/Universum/Exception.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE CPP                   #-}
+{-# LANGUAGE DerivingStrategies    #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE PatternSynonyms       #-}
 {-# LANGUAGE Safe                  #-}
@@ -9,25 +9,21 @@
 
 module Universum.Exception
        ( module Control.Exception.Safe
-#if ( __GLASGOW_HASKELL__ >= 800 )
        , Bug (..)
        , bug
        , pattern Exc
-#endif
        , note
        ) where
 
 -- exceptions from safe-exceptions
 import Control.Exception.Safe (Exception (..), MonadCatch, MonadMask (..), MonadThrow,
                                SomeException (..), bracket, bracketOnError, bracket_, catch,
-                               catchAny, displayException, finally, handleAny, mask_, onException,
-                               throwM, try, tryAny)
-
+                               catchAny, displayException, finally, handleAny, onException, throwM,
+                               try, tryAny)
 import Control.Monad.Except (MonadError, throwError)
 import Universum.Applicative (Applicative (pure))
 import Universum.Monad (Maybe (..), maybe)
 
-#if ( __GLASGOW_HASKELL__ >= 800 )
 import Data.List ((++))
 import GHC.Show (Show)
 import GHC.Stack (CallStack, HasCallStack, callStack, prettyCallStack)
@@ -37,7 +33,7 @@
 -- | Type that represents exceptions used in cases when a particular codepath
 -- is not meant to be ever executed, but happens to be executed anyway.
 data Bug = Bug SomeException CallStack
-    deriving (Show)
+    deriving stock (Show)
 
 instance Exception Bug where
     displayException (Bug e cStack) = Safe.displayException e ++ "\n"
@@ -47,21 +43,15 @@
 -- throw the exception wrapped into 'Bug' data type.
 bug :: (HasCallStack, Exception e) => e -> a
 bug e = Safe.impureThrow (Bug (Safe.toException e) callStack)
-#endif
 
 -- To suppress redundant applicative constraint warning on GHC 8.0
 -- | Throws error for 'Maybe' if 'Data.Maybe.Nothing' is given.
 -- Operates over 'MonadError'.
-#if ( __GLASGOW_HASKELL__ >= 800 )
+{-# DEPRECATED note "Don't use this function. Use 'maybeToRight' instead" #-}
 note :: (MonadError e m) => e -> Maybe a -> m a
 note err = maybe (throwError err) pure
-#else
-note :: (MonadError e m, Applicative m) => e -> Maybe a -> m a
-note err = maybe (throwError err) pure
-#endif
 
 
-#if ( __GLASGOW_HASKELL__ >= 800 )
 {- | Pattern synonym to easy pattern matching on exceptions. So intead of
 writing something like this:
 
@@ -87,4 +77,3 @@
 pattern Exc e <- (fromException -> Just e)
   where
     Exc e = toException e
-#endif
diff --git a/src/Universum/Function.hs b/src/Universum/Function.hs
--- a/src/Universum/Function.hs
+++ b/src/Universum/Function.hs
@@ -1,13 +1,9 @@
+{-# LANGUAGE Safe #-}
+
 -- | This module reexports very basic and primitive functions and function combinators.
 
 module Universum.Function
        ( module Data.Function
-       , identity
        ) where
 
-import Data.Function (const, fix, flip, id, on, ($), (.))
-
--- | Renamed version of 'Prelude.id'.
-identity :: a -> a
-identity = id
-{-# INLINE identity #-}
+import Data.Function (const, fix, flip, id, on, ($), (.), (&))
diff --git a/src/Universum/Functor/Reexport.hs b/src/Universum/Functor/Reexport.hs
--- a/src/Universum/Functor/Reexport.hs
+++ b/src/Universum/Functor/Reexport.hs
@@ -12,6 +12,6 @@
 
 import Control.Arrow ((&&&))
 import Data.Bifunctor (Bifunctor (..))
-import Data.Functor (Functor (..), void, ($>), (<$>))
+import Data.Functor (Functor (..), void, ($>), (<$>), (<&>))
 import Data.Functor.Compose (Compose (..))
 import Data.Functor.Identity (Identity (..))
diff --git a/src/Universum/Lifted.hs b/src/Universum/Lifted.hs
--- a/src/Universum/Lifted.hs
+++ b/src/Universum/Lifted.hs
@@ -7,11 +7,9 @@
        , module Universum.Lifted.Env
        , module Universum.Lifted.File
        , module Universum.Lifted.IORef
-       , module Universum.Lifted.ST
        ) where
 
 import Universum.Lifted.Concurrent
 import Universum.Lifted.Env
 import Universum.Lifted.File
 import Universum.Lifted.IORef
-import Universum.Lifted.ST
diff --git a/src/Universum/Lifted/Concurrent.hs b/src/Universum/Lifted/Concurrent.hs
--- a/src/Universum/Lifted/Concurrent.hs
+++ b/src/Universum/Lifted/Concurrent.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE Safe #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE Safe         #-}
 
 -- | Concurrency useful and common functions.
 
@@ -14,6 +15,7 @@
        , tryPutMVar
        , tryReadMVar
        , tryTakeMVar
+       , updateMVar'
 
          -- * STM
        , STM
@@ -22,6 +24,7 @@
        , newTVarIO
        , readTVarIO
        , STM.modifyTVar'
+       , updateTVar'
        , STM.newTVar
        , STM.readTVar
        , STM.writeTVar
@@ -29,14 +32,18 @@
 
 import Control.Concurrent.MVar (MVar)
 import Control.Concurrent.STM.TVar (TVar)
+import Control.Monad (return)
 import Control.Monad.STM (STM)
+import Control.Monad.State (StateT (..))
 import Control.Monad.Trans (MonadIO, liftIO)
 import Data.Bool (Bool)
 import Data.Function (($), (.))
 import Data.Maybe (Maybe)
+import System.IO (IO)
 
-import qualified Control.Concurrent.MVar as CCM (newEmptyMVar, newMVar, putMVar, readMVar, swapMVar,
-                                                 takeMVar, tryPutMVar, tryReadMVar, tryTakeMVar)
+import qualified Control.Concurrent.MVar as CCM (modifyMVar, newEmptyMVar, newMVar, putMVar,
+                                                 readMVar, swapMVar, takeMVar, tryPutMVar,
+                                                 tryReadMVar, tryTakeMVar)
 import qualified Control.Concurrent.STM.TVar as STM (modifyTVar', newTVar, newTVarIO, readTVar,
                                                      readTVarIO, writeTVar)
 import qualified Control.Monad.STM as STM (atomically)
@@ -108,3 +115,26 @@
 readTVarIO :: MonadIO m => TVar a -> m a
 readTVarIO = liftIO . STM.readTVarIO
 {-# INLINE readTVarIO #-}
+
+----------------------------------------------------------------------------
+-- Common helpers
+----------------------------------------------------------------------------
+
+-- | Like 'modifyMVar', but modification is specified as a 'State' computation.
+--
+-- This method is strict in produced @s@ value.
+updateMVar' :: MonadIO m => MVar s -> StateT s IO a -> m a
+updateMVar' var (StateT f) =
+  liftIO . CCM.modifyMVar var $ \s -> do
+    (a, !s') <- f s
+    return (s', a)
+{-# INLINE updateMVar' #-}
+
+-- | Like 'modifyTVar\'', but modification is specified as a 'State' monad.
+updateTVar' :: TVar s -> StateT s STM a -> STM a
+updateTVar' var (StateT f) = do
+  s <- STM.readTVar var
+  (a, !s') <- f s
+  STM.writeTVar var s'
+  return a
+{-# INLINE updateTVar' #-}
diff --git a/src/Universum/Lifted/Env.hs b/src/Universum/Lifted/Env.hs
--- a/src/Universum/Lifted/Env.hs
+++ b/src/Universum/Lifted/Env.hs
@@ -3,8 +3,7 @@
 -- | Lifted versions of functions that work with environment.
 
 module Universum.Lifted.Env
-       ( getArgs
-       , exitWith
+       ( exitWith
        , exitFailure
        , exitSuccess
        , die
@@ -16,15 +15,9 @@
 import System.Exit (ExitCode)
 import System.IO (stderr)
 
-import qualified System.Environment as XIO
 import qualified System.Exit as XIO
 import qualified System.IO (hPutStrLn)
 
--- | Lifted version of 'System.Environment.getArgs'.
-getArgs :: MonadIO m => m [String]
-getArgs = liftIO (XIO.getArgs)
-{-# INLINE getArgs #-}
-
 -- | Lifted version of 'System.Exit.exitWith'.
 exitWith :: MonadIO m => ExitCode -> m a
 exitWith a = liftIO (XIO.exitWith a)
@@ -43,6 +36,6 @@
 -- | Lifted version of 'System.Exit.die'.
 -- 'XIO.die' is available since base-4.8, but it's more convenient to
 -- redefine it instead of using CPP.
-die :: MonadIO m => String -> m ()
+die :: MonadIO m => String -> m a
 die err = liftIO (System.IO.hPutStrLn stderr err) >> exitFailure
 {-# INLINE die #-}
diff --git a/src/Universum/Lifted/File.hs b/src/Universum/Lifted/File.hs
--- a/src/Universum/Lifted/File.hs
+++ b/src/Universum/Lifted/File.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP  #-}
 {-# LANGUAGE Safe #-}
 
 -- | Lifted versions of functions working with files and common IO.
@@ -6,61 +5,80 @@
 
 module Universum.Lifted.File
        ( appendFile
-       , getContents
        , getLine
-       , interact
-       , openFile
        , readFile
        , writeFile
+       , withFile
+       , openFile
+       , hClose
        ) where
 
+import Control.Exception.Safe (MonadMask, bracket)
 import Control.Monad.Trans (MonadIO, liftIO)
+import Data.Function ((.))
 import Data.Text (Text)
 import Prelude (FilePath)
 import System.IO (Handle, IOMode)
 
 import qualified Data.Text.IO as XIO
-import qualified Data.Text.Lazy as L (Text)
-import qualified Data.Text.Lazy.IO as LIO (getContents, interact)
-import qualified System.IO as XIO (openFile)
+import qualified System.IO as XIO (IO, hClose, openFile)
 
 ----------------------------------------------------------------------------
 -- Text
 ----------------------------------------------------------------------------
 
--- | Lifted version of 'Data.Text.appendFile'.
+-- | Lifted version of 'Data.Text.IO.appendFile'.
 appendFile :: MonadIO m => FilePath -> Text -> m ()
 appendFile a b = liftIO (XIO.appendFile a b)
 {-# INLINE appendFile #-}
 
--- | Lifted version of 'Data.Text.getContents'.
-getContents :: MonadIO m => m L.Text
-getContents = liftIO LIO.getContents
-{-# INLINE getContents #-}
-
--- | Lifted version of 'Data.Text.getLine'.
+-- | Lifted version of 'Data.Text.IO.getLine'.
 getLine :: MonadIO m => m Text
 getLine = liftIO XIO.getLine
 {-# INLINE getLine #-}
 
--- | Lifted version of 'Data.Text.interact'.
-interact :: MonadIO m => (L.Text -> L.Text) -> m ()
-interact a = liftIO (LIO.interact a)
-{-# INLINE interact #-}
-
--- | Lifted version of 'Data.Text.readFile'.
+-- | Lifted version of 'Data.Text.IO.readFile'.
 readFile :: MonadIO m => FilePath -> m Text
 readFile a = liftIO (XIO.readFile a)
 {-# INLINE readFile #-}
 
--- | Lifted version of 'Data.Text.writeFile'.
+-- | Lifted version of 'Data.Text.IO.writeFile'.
 writeFile :: MonadIO m => FilePath -> Text -> m ()
 writeFile a b = liftIO (XIO.writeFile a b)
 {-# INLINE writeFile #-}
 
 -- | Lifted version of 'System.IO.openFile'.
+--
+-- See also 'withFile' for more information.
 openFile :: MonadIO m => FilePath -> IOMode -> m Handle
 openFile a b = liftIO (XIO.openFile a b)
 {-# INLINE openFile #-}
 
--- 'withFile' can't be lifted into 'MonadIO', as it uses 'bracket'
+-- | Close a file handle
+--
+-- See also 'withFile' for more information.
+hClose :: MonadIO m => Handle -> m ()
+hClose = liftIO . XIO.hClose
+{-# INLINE hClose #-}
+
+-- | Opens a file, manipulates it with the provided function and closes the
+-- handle before returning. The 'Handle' can be written to using the
+-- 'Universum.Print.hPutStr' and 'Universum.Print.hPutStrLn' functions.
+--
+-- 'withFile' is essentially the 'bracket' pattern, specialized to files. This
+-- should be preferred over 'openFile' + 'hClose' as it properly deals with
+-- (asynchronous) exceptions. In cases where 'withFile' is insufficient, for
+-- instance because the it is not statically known when manipulating the
+-- 'Handle' has finished, one should consider other safe paradigms for resource
+-- usage, such as the @ResourceT@ transformer from the @resourcet@ package,
+-- before resorting to 'openFile' and 'hClose'.
+withFile :: (MonadIO m, MonadMask m) => FilePath -> IOMode -> (Handle -> m a) -> m a
+withFile filePath mode f = bracket (openFile filePath mode) hClose f
+
+{-# SPECIALIZE appendFile :: FilePath -> Text -> XIO.IO () #-}
+{-# SPECIALIZE getLine :: XIO.IO Text #-}
+{-# SPECIALIZE readFile :: FilePath -> XIO.IO Text #-}
+{-# SPECIALIZE writeFile :: FilePath -> Text -> XIO.IO () #-}
+{-# SPECIALIZE openFile :: FilePath -> IOMode -> XIO.IO Handle #-}
+{-# SPECIALIZE hClose :: Handle -> XIO.IO () #-}
+{-# SPECIALIZE withFile :: FilePath -> IOMode -> (Handle -> XIO.IO a) -> XIO.IO a #-}
diff --git a/src/Universum/Lifted/ST.hs b/src/Universum/Lifted/ST.hs
deleted file mode 100644
--- a/src/Universum/Lifted/ST.hs
+++ /dev/null
@@ -1,14 +0,0 @@
--- | This module contains lifted version of 'XIO.stToIO' function.
-
-module Universum.Lifted.ST
-       ( stToIO
-       ) where
-
-import Control.Monad.Trans (MonadIO, liftIO)
-
-import qualified Control.Monad.ST as XIO
-
--- | Lifted version of 'XIO.stToIO'.
-stToIO :: MonadIO m => XIO.ST XIO.RealWorld a -> m a
-stToIO a = liftIO (XIO.stToIO a)
-{-# INLINE stToIO #-}
diff --git a/src/Universum/List/Reexport.hs b/src/Universum/List/Reexport.hs
--- a/src/Universum/List/Reexport.hs
+++ b/src/Universum/List/Reexport.hs
@@ -1,26 +1,18 @@
-{-# LANGUAGE CPP         #-}
 {-# LANGUAGE Trustworthy #-}
 
 -- | This module reexports functinons to work with list, 'NonEmpty' and String types.
 
 module Universum.List.Reexport
        ( module Data.List
-
-#if ( __GLASGOW_HASKELL__ >= 800 )
        , module Data.List.NonEmpty
-#endif
-
        , module GHC.Exts
        ) where
 
 import Data.List (break, cycle, drop, dropWhile, filter, genericDrop, genericLength,
-                  genericReplicate, genericSplitAt, genericTake, group, inits, intercalate,
+                  genericReplicate, genericSplitAt, genericTake, inits, intercalate,
                   intersperse, isPrefixOf, iterate, permutations, repeat, replicate, reverse, scanl,
                   scanr, sort, sortBy, sortOn, splitAt, subsequences, tails, take, takeWhile,
                   transpose, unfoldr, unzip, unzip3, zip, zip3, zipWith, (++))
-
-#if ( __GLASGOW_HASKELL__ >= 800 )
-import Data.List.NonEmpty (NonEmpty (..), head, init, last, nonEmpty, tail)
-#endif
+import Data.List.NonEmpty (NonEmpty (..), group, groupAllWith, groupBy, groupWith, head, init, last, nonEmpty, tail)
 
 import GHC.Exts (sortWith)
diff --git a/src/Universum/List/Safe.hs b/src/Universum/List/Safe.hs
--- a/src/Universum/List/Safe.hs
+++ b/src/Universum/List/Safe.hs
@@ -1,46 +1,34 @@
-{-# LANGUAGE CPP         #-}
-{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE Safe #-}
 
 -- | This module contains safe functions to work with list type (mostly with 'NonEmpty').
 
 module Universum.List.Safe
-       ( list
-       , uncons
-#if ( __GLASGOW_HASKELL__ >= 800 )
+       ( uncons
        , whenNotNull
        , whenNotNullM
-#endif
+       , foldr1
+       , foldl1
+       , minimum
+       , maximum
+       , minimumBy
+       , maximumBy
        ) where
 
-#if ( __GLASGOW_HASKELL__ >= 800 )
+import qualified Data.Foldable as F
+import Data.Ord (Ord, Ordering)
+
 import Universum.Applicative (Applicative, pass)
 import Universum.List.Reexport (NonEmpty (..))
-import Universum.Monad (Monad (..))
-#endif
-
-import Universum.Functor (fmap)
-import Universum.Monad (Maybe (..))
+import Universum.Monad (Maybe (..), Monad (..))
 
 -- $setup
 -- >>> import Universum.Applicative (pure)
--- >>> import Universum.Base ((==), even)
+-- >>> import Universum.Base ((==), (+), even)
 -- >>> import Universum.Bool (Bool (..), not)
 -- >>> import Universum.Container (length)
 -- >>> import Universum.Function (($))
 -- >>> import Universum.Print (print)
 
--- | Returns default list if given list is empty.
--- Otherwise applies given function to every element.
---
--- >>> list [True] even []
--- [True]
--- >>> list [True] even [1..5]
--- [False,True,False,True,False]
-list :: [b] -> (a -> b) -> [a] -> [b]
-list def f xs = case xs of
-    [] -> def
-    _  -> fmap f xs
-
 -- | Destructuring list into its head and tail if possible. This function is total.
 --
 -- >>> uncons []
@@ -53,7 +41,6 @@
 uncons []     = Nothing
 uncons (x:xs) = Just (x, xs)
 
-#if ( __GLASGOW_HASKELL__ >= 800 )
 {- | Performs given action over 'NonEmpty' list if given list is non empty.
 
 >>> whenNotNull [] $ \(b :| _) -> print (not b)
@@ -70,4 +57,49 @@
 whenNotNullM :: Monad m => m [a] -> (NonEmpty a -> m ()) -> m ()
 whenNotNullM ml f = ml >>= \l -> whenNotNull l f
 {-# INLINE whenNotNullM #-}
-#endif
+
+-- | A variant of 'foldl' that has no base case, and thus may only be
+-- applied to 'NonEmpty'.
+--
+-- >>> foldl1 (+) (1 :| [2,3,4,5])
+-- 15
+foldl1 :: (a -> a -> a) -> NonEmpty a -> a
+foldl1 = F.foldl1
+{-# INLINE foldl1 #-}
+
+-- | A variant of 'foldr' that has no base case, and thus may only be
+-- applied to 'NonEmpty'.
+--
+-- >>> foldr1 (+) (1 :| [2,3,4,5])
+-- 15
+foldr1 :: (a -> a -> a) -> NonEmpty a -> a
+foldr1 = F.foldr1
+{-# INLINE foldr1 #-}
+
+-- | The least element of a 'NonEmpty' with respect to the given
+-- comparison function.
+minimumBy :: (a -> a -> Ordering) -> NonEmpty a -> a
+minimumBy = F.minimumBy
+{-# INLINE minimumBy #-}
+
+-- | The least element of a 'NonEmpty'.
+--
+-- >>> minimum (1 :| [2,3,4,5])
+-- 1
+minimum :: Ord a => NonEmpty a -> a
+minimum = F.minimum
+{-# INLINE minimum #-}
+
+-- | The largest element of a 'NonEmpty' with respect to the given
+-- comparison function.
+maximumBy :: (a -> a -> Ordering) -> NonEmpty a -> a
+maximumBy = F.maximumBy
+{-# INLINE maximumBy #-}
+
+-- | The largest element of a 'NonEmpty'.
+--
+-- >>> maximum (1 :| [2,3,4,5])
+-- 5
+maximum :: Ord a => NonEmpty a -> a
+maximum = F.maximum
+{-# INLINE maximum #-}
diff --git a/src/Universum/Monad.hs b/src/Universum/Monad.hs
--- a/src/Universum/Monad.hs
+++ b/src/Universum/Monad.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE Safe #-}
 
 -- | Reexporting useful monadic stuff.
 
diff --git a/src/Universum/Monad/Container.hs b/src/Universum/Monad/Container.hs
--- a/src/Universum/Monad/Container.hs
+++ b/src/Universum/Monad/Container.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP          #-}
+{-# LANGUAGE Trustworthy  #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | This module exports functions which allow to process instances of
@@ -17,6 +19,9 @@
 import Data.Function ((.))
 import Data.Traversable (Traversable (traverse))
 import Prelude (Bool (..), Monoid, flip)
+#if MIN_VERSION_base(4,17,0)
+import Data.Type.Equality (type (~))
+#endif
 
 import Universum.Base (IO)
 import Universum.Container (Container, Element, fold, toList)
diff --git a/src/Universum/Monad/Maybe.hs b/src/Universum/Monad/Maybe.hs
--- a/src/Universum/Monad/Maybe.hs
+++ b/src/Universum/Monad/Maybe.hs
@@ -13,8 +13,7 @@
        ) where
 
 import Universum.Applicative (Applicative, pass, pure)
-import Universum.Monad.Reexport (fromMaybe)
-import Universum.Monad.Reexport (Maybe (..), Monad (..))
+import Universum.Monad.Reexport (Maybe (..), Monad (..), fromMaybe)
 
 -- $setup
 -- >>> import Universum.Bool (Bool (..), not)
diff --git a/src/Universum/Monad/Reexport.hs b/src/Universum/Monad/Reexport.hs
--- a/src/Universum/Monad/Reexport.hs
+++ b/src/Universum/Monad/Reexport.hs
@@ -1,5 +1,4 @@
-{-# LANGUAGE CPP         #-}
-{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE Safe #-}
 
 -- | This module reexports functions to work with monads.
 
@@ -58,42 +57,11 @@
 import Control.Monad.Trans.Maybe (MaybeT (..), exceptToMaybeT, maybeToExceptT)
 
 -- Maybe
-import Data.Maybe (Maybe (..), catMaybes, fromMaybe, isJust, isNothing, listToMaybe, mapMaybe,
+import Data.Maybe (Maybe (..), catMaybes, fromMaybe, isJust, isNothing, mapMaybe,
                    maybe, maybeToList)
 
 -- Either
 import Data.Either (Either (..), either, isLeft, isRight, lefts, partitionEithers, rights)
 
 import Control.Monad hiding (fail)
-
-#if __GLASGOW_HASKELL__ >= 800
 import Control.Monad.Fail (MonadFail (..))
-#else
-import Prelude (String)
-import Text.ParserCombinators.ReadP (ReadP)
-import Text.ParserCombinators.ReadPrec (ReadPrec)
-
-import Universum.Base (IO)
-
-import qualified Prelude as P (fail)
-
--- | Class for 'Monad's that can 'fail'.
--- Copied from 'fail' by Herbert Valerio Riedel (the library is under BSD3).
-class Monad m => MonadFail m where
-    fail :: String -> m a
-
-instance MonadFail Maybe where
-    fail _ = Nothing
-
-instance MonadFail [] where
-    fail _ = []
-
-instance MonadFail IO where
-    fail = P.fail
-
-instance MonadFail ReadPrec where
-    fail = P.fail -- = P (\_ -> fail s)
-
-instance MonadFail ReadP where
-    fail = P.fail
-#endif
diff --git a/src/Universum/Monad/Trans.hs b/src/Universum/Monad/Trans.hs
--- a/src/Universum/Monad/Trans.hs
+++ b/src/Universum/Monad/Trans.hs
@@ -14,13 +14,17 @@
        , executingStateT
        , usingState
        , usingStateT
+       
+         -- * Convenient functions to work with 'MaybeT' and 'ExceptT' transformers
+       , hoistMaybe
+       , hoistEither
        ) where
 
-import Prelude (flip, fst, snd)
+import Prelude (Applicative (..), flip, fst, snd)
 
 import Universum.Functor (Functor, (<$>))
-import Universum.Monad.Reexport (Reader, ReaderT, State, StateT, runReader, runReaderT, runState,
-                                 runStateT)
+import Universum.Monad.Reexport (Either, ExceptT (..), Maybe, MaybeT (..), Reader, ReaderT, State,
+                                 StateT, runReader, runReaderT, runState, runStateT)
 
 -- | Shorter and more readable alias for @flip runReaderT@.
 usingReaderT :: r -> ReaderT r m a -> m a
@@ -65,3 +69,13 @@
 executingState :: s -> State s a -> s
 executingState s st = snd (usingState s st)
 {-# INLINE executingState #-}
+
+-- | Lift a 'Maybe' to the 'MaybeT' monad
+hoistMaybe  :: Applicative m => Maybe a -> MaybeT m a
+hoistMaybe m = MaybeT (pure m)
+{-# INLINE hoistMaybe #-}
+
+-- | Lift a 'Either' to the 'ExceptT' monad
+hoistEither :: Applicative m => Either e a -> ExceptT e m a
+hoistEither e = ExceptT (pure e)
+{-# INLINE hoistEither #-}
diff --git a/src/Universum/Monoid.hs b/src/Universum/Monoid.hs
--- a/src/Universum/Monoid.hs
+++ b/src/Universum/Monoid.hs
@@ -1,22 +1,17 @@
-{-# LANGUAGE CPP #-}
+{-# LANGUAGE Safe #-}
 
 -- | This module reexports functions to work with monoids plus adds extra useful functions.
 
 module Universum.Monoid
        ( module Data.Monoid
-#if ( __GLASGOW_HASKELL__ >= 800 )
        , module Data.Semigroup
-#endif
        , maybeToMonoid
        ) where
 
 import Data.Monoid (All (..), Alt (..), Any (..), Dual (..), Endo (..), First (..), Last (..),
                     Monoid (..), Product (..), Sum (..))
-
-#if ( __GLASGOW_HASKELL__ >= 800 )
-import Data.Semigroup (Option (..), Semigroup (sconcat, stimes, (<>)), WrappedMonoid, cycle1,
-                       mtimesDefault, stimesIdempotent, stimesIdempotentMonoid, stimesMonoid)
-#endif
+import Data.Semigroup (Semigroup (sconcat, stimes, (<>)), WrappedMonoid, cycle1, mtimesDefault,
+                       stimesIdempotent, stimesIdempotentMonoid, stimesMonoid)
 
 import Universum.Monad.Reexport (Maybe, fromMaybe)
 
diff --git a/src/Universum/Nub.hs b/src/Universum/Nub.hs
--- a/src/Universum/Nub.hs
+++ b/src/Universum/Nub.hs
@@ -1,7 +1,10 @@
+{-# LANGUAGE Safe #-}
+{-# LANGUAGE CPP #-}
+
 {-| Functions to remove duplicates from a list.
 
  = Performance
- To check the performance there was done a bunch of benchmarks.
+ To check the performance many benchmarks were done.
  Benchmarks were made on lists of 'Prelude.Int's and 'Data.Text.Text's.
  There were two types of list to use:
 
@@ -28,30 +31,16 @@
        , unstableNub
        ) where
 
+#if !MIN_VERSION_hashable(1,4,0)
 import Data.Eq (Eq)
+#endif
 import Data.Hashable (Hashable)
 import Data.HashSet as HashSet
 import Data.Ord (Ord)
-import Data.Set (Set)
-import Prelude (Bool, Char, (.))
+import Prelude ((.))
 
 import qualified Data.Set as Set
 
--- Liquid Haskell check for duplicates.
-{-@ type ListUnique a = {v : [a] | NoDups v} @-}
-
-{-@ predicate NoDups L = Set_emp (dups L) @-}
-
-{-@ measure dups :: [a] -> (Set a)
-    dups ([])   = {v | Set_emp v}
-    dups (x:xs) = {v | v =
-      if (Set_mem x (listElts xs))
-      then (Set_cup (Set_sng x) (dups xs))
-      else (dups xs)}
-@-}
-
-{-@ Set.toList :: Set a -> ListUnique a @-}
-
 -- | Like 'Prelude.nub' but runs in @O(n * log n)@ time and requires 'Ord'.
 --
 -- >>> ordNub [3, 3, 3, 2, 2, -1, 1]
@@ -69,7 +58,11 @@
 --
 -- >>> hashNub [3, 3, 3, 2, 2, -1, 1]
 -- [3,2,-1,1]
+#if MIN_VERSION_hashable(1,4,0)
+hashNub :: (Hashable a) => [a] -> [a]
+#else
 hashNub :: (Eq a, Hashable a) => [a] -> [a]
+#endif
 hashNub = go HashSet.empty
   where
     go _ []     = []
@@ -82,7 +75,6 @@
 --
 -- >>> sortNub [3, 3, 3, 2, 2, -1, 1]
 -- [-1,1,2,3]
-{-@ sortNub :: [a] -> ListUnique a @-}
 sortNub :: (Ord a) => [a] -> [a]
 sortNub = Set.toList . Set.fromList
 
@@ -90,5 +82,9 @@
 --
 -- >>> unstableNub [3, 3, 3, 2, 2, -1, 1]
 -- [1,2,3,-1]
+#if MIN_VERSION_hashable(1,4,0)
+unstableNub :: (Hashable a) => [a] -> [a]
+#else
 unstableNub :: (Eq a, Hashable a) => [a] -> [a]
+#endif
 unstableNub = HashSet.toList . HashSet.fromList
diff --git a/src/Universum/Print.hs b/src/Universum/Print.hs
--- a/src/Universum/Print.hs
+++ b/src/Universum/Print.hs
@@ -1,64 +1,90 @@
+{-|
+Module      : $header$
+Description : Overloaded writing for String-like types
+Copyright   : (c) Serokell 2018
+License     : MIT
+Maintainer  : Serokell <hi@serokell.io>
+Stability   : experimental
+Portability : portable
+
+Generalizes the 'Base.putStr' family of functions for String likes such as lazy
+and strict versions of 'Text' and 'ByteString'.
+
+A caveat to using overloaded functions is that printing string literals raises
+ambiguity errors in the presence of @OverloadedStrings@. To avoid this problem
+wither add a type annotation @putStr ("Hello World!" :: Text)@ or use one of the
+type constrained functions 'putText', 'putLText' etc.
+
+You may add support for your own types by importing "Universum.Print.Internal"
+and implementing 'Print'. However be advised that only the functions in this
+module should be considered stable, not the interface for 'Print'.
+-}
 {-# LANGUAGE ExplicitForAll    #-}
 {-# LANGUAGE FlexibleContexts  #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE Trustworthy       #-}
 
--- | Generalization of 'Prelude.putStr' and 'Prelude.putStrLn' functions.
-
 module Universum.Print
-       ( Print (..)
+       ( putStr
+       , putStrLn
        , print
+       , Print
        , putText
        , putTextLn
        , putLText
        , putLTextLn
+       -- ** Writing strings to an arbitrary 'Handle'
+       , hPutStr
+       , hPutStrLn
+       , hPrint
        ) where
 
 import Data.Function ((.))
 
 import Universum.Monad.Reexport (MonadIO, liftIO)
 
-import qualified Prelude (print, putStr, putStrLn)
+import Universum.Print.Internal (Print)
+import qualified Universum.Print.Internal as I (hPutStrLn, hPutStr)
 
-import qualified Data.ByteString.Char8 as BS
-import qualified Data.ByteString.Lazy.Char8 as BL
+import qualified Prelude (print)
+import qualified System.IO as SIO (Handle, hPrint)
 
 import qualified Data.Text as T
-import qualified Data.Text.IO as T
 
 import qualified Data.Text.Lazy as TL
-import qualified Data.Text.Lazy.IO as TL
 
 import qualified Universum.Base as Base
 
--- | Polymorfic over string and lifted to 'MonadIO' printing functions.
-class Print a where
-  putStr :: MonadIO m => a -> m ()
-  putStrLn :: MonadIO m => a -> m ()
-
-instance Print T.Text where
-  putStr = liftIO . T.putStr
-  putStrLn = liftIO . T.putStrLn
-
-instance Print TL.Text where
-  putStr = liftIO . TL.putStr
-  putStrLn = liftIO . TL.putStrLn
+-- | Write a string like value @a@ to a supplied 'SIO.Handle'.
+hPutStr :: (Print a, MonadIO m) => SIO.Handle -> a -> m ()
+hPutStr h = liftIO . I.hPutStr h
+{-# SPECIALIZE hPutStr :: Print a => SIO.Handle -> a -> Base.IO () #-}
 
-instance Print BS.ByteString where
-  putStr = liftIO . BS.putStr
-  putStrLn = liftIO . BS.putStrLn
+-- | Write a string like value @a@ to a supplied 'SIO.Handle', appending a
+-- newline character.
+hPutStrLn :: (Print a, MonadIO m) => SIO.Handle -> a -> m ()
+hPutStrLn h = liftIO . I.hPutStrLn h
+{-# SPECIALIZE hPutStrLn :: Print a => SIO.Handle -> a -> Base.IO () #-}
 
-instance Print BL.ByteString where
-  putStr = liftIO . BL.putStr
-  putStrLn = liftIO . BL.putStrLn
+-- | Write a string like value to @stdout@/.
+putStr :: (Print a, MonadIO m) => a -> m ()
+putStr = hPutStr Base.stdout
+{-# SPECIALIZE putStr :: Print a => a -> Base.IO () #-}
 
-instance Print [Base.Char] where
-  putStr = liftIO . Prelude.putStr
-  putStrLn = liftIO . Prelude.putStrLn
+-- | Write a string like value to @stdout@ appending a newline character.
+putStrLn :: (Print a, MonadIO m) => a -> m ()
+putStrLn = hPutStrLn Base.stdout
+{-# SPECIALIZE putStrLn :: Print a => a -> Base.IO () #-}
 
 -- | Lifted version of 'Prelude.print'.
 print :: forall a m . (MonadIO m, Base.Show a) => a -> m ()
 print = liftIO . Prelude.print
+{-# SPECIALIZE print :: Base.Show a => a -> Base.IO () #-}
+
+-- | Lifted version of 'System.IO.hPrint'
+hPrint :: (MonadIO m, Base.Show a) => SIO.Handle -> a -> m ()
+hPrint h = liftIO . SIO.hPrint h
+{-# SPECIALIZE hPrint :: Base.Show a => SIO.Handle -> a -> Base.IO () #-}
 
 -- | Specialized to 'T.Text' version of 'putStr' or forcing type inference.
 putText :: MonadIO m => T.Text -> m ()
diff --git a/src/Universum/Print/Internal.hs b/src/Universum/Print/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Universum/Print/Internal.hs
@@ -0,0 +1,55 @@
+{-|
+Module      : $header$
+Description : Class machinery for overloaded writing of String-like types
+Copyright   : (c) Justus Adam 2018
+License     : MIT
+Maintainer  : Serokell <hi@serokell.io>
+Stability   : experimental
+Portability : portable
+
+You may import this module to define your own, custom instances of 'Print'. Be
+advised however that this module is an internal API and may be subject to change
+__even for minor version increments__.
+-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE Trustworthy       #-}
+
+module Universum.Print.Internal (Print(..)) where
+
+import qualified System.IO as SIO (Handle, hPutStr, hPutStrLn)
+
+import qualified Data.ByteString.Char8 as BS
+import qualified Data.ByteString.Lazy.Char8 as BL
+
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.IO as TL
+
+import qualified Universum.Base as Base
+
+-- | Support class to overload writing of string like values.
+class Print a where
+  hPutStr :: SIO.Handle -> a -> Base.IO ()
+  hPutStrLn :: SIO.Handle -> a -> Base.IO ()
+
+instance Print T.Text where
+  hPutStr = T.hPutStr
+  hPutStrLn = T.hPutStrLn
+
+instance Print TL.Text where
+  hPutStr = TL.hPutStr
+  hPutStrLn = TL.hPutStrLn
+
+instance Print BS.ByteString where
+  hPutStr = BS.hPutStr
+  hPutStrLn = BS.hPutStrLn
+
+instance Print BL.ByteString where
+  hPutStr = BL.hPutStr
+  hPutStrLn = BL.hPutStrLn
+
+instance Print [Base.Char] where
+  hPutStr = SIO.hPutStr
+  hPutStrLn = SIO.hPutStrLn
diff --git a/src/Universum/String.hs b/src/Universum/String.hs
--- a/src/Universum/String.hs
+++ b/src/Universum/String.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE Safe #-}
+
 -- | Type classes for convertion between different string representations.
 
 module Universum.String
diff --git a/src/Universum/String/Conversion.hs b/src/Universum/String/Conversion.hs
--- a/src/Universum/String/Conversion.hs
+++ b/src/Universum/String/Conversion.hs
@@ -2,7 +2,8 @@
 {-# LANGUAGE ExplicitForAll        #-}
 {-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE TypeSynonymInstances  #-}
+{-# LANGUAGE Trustworthy           #-}
+{-# OPTIONS_GHC -Wno-orphans  #-}
 
 -- | This module implements type class which allow to have conversion to and
 -- from 'Text', 'String' and 'ByteString' types (including both strict and lazy
@@ -23,14 +24,21 @@
 
          -- * Show and read functions
        , readEither
+       , readMaybe
        , show
        ) where
 
 import Data.Bifunctor (first)
 import Data.Either (Either)
 import Data.Function (id, (.))
+import Data.Maybe (Maybe)
 import Data.String (String)
+import qualified Data.Text.Internal as T
 
+#if !MIN_VERSION_text(2,0,2)
+import qualified Data.Text.Internal.Fusion.Common as TF
+#endif
+
 import Universum.Functor ((<$>))
 import Universum.String.Reexport (ByteString, IsString, Read, Text, fromString)
 
@@ -43,7 +51,7 @@
 import qualified Data.Text.Encoding.Error as T
 import qualified Data.Text.Lazy as LT
 import qualified Data.Text.Lazy.Encoding as LT
-import qualified Text.Read (readEither)
+import qualified Text.Read (readEither, readMaybe)
 
 import qualified GHC.Show as Show (Show (show))
 
@@ -159,14 +167,140 @@
 instance ToString LT.Text where
     toString = LT.unpack
 
+{-
+
+@toString . toText@ pattern may occur quite often after inlining because
+we tend to use 'Text' rather than 'String' in function signatures, but
+there are still some libraries which use 'String's and thus make us perform
+conversions back and forth.
+
+Note that, as documentation for 'T.pack' function mentions, @toString . toText@
+is not strictly equal to the identity function. Thus, replacing @toString . toText@
+with @id@ will result in losing transformation of surrogate code points.
+But in the most cases this is what the user wants.
+-}
+
+{-# RULES "pack/unpack" [~0]
+    forall s. T.unpack (T.pack s) = s
+#-}
+
+-- This applies when `text` is older than 2.0.2, the oldest GHC version
+-- with such `text` is 9.4.4
+-- TODO: remove once we stop supporting GHC 9.4 and older.
+#if !MIN_VERSION_text(2,0,2)
+{-
+
+We can do even better than above if take rules defined in 'Data.Text' into
+account.
+
+Quoting investigation of @int-index:
+
+If we look at @unpack@ and @pack@ they are defined as
+
+@
+unpack = S.unstreamList . stream
+{-# INLINE [1] unpack #-}
+
+pack = unstream . S.map safe . S.streamList
+{-# INLINE [1] pack #-}
+@
+
+After they get inlined, the rule seems to be
+
+@
+(S.unstreamList . stream) ((unstream . S.map safe . S.streamList) a)
+@
+
+If we also inline function composition, we get
+
+@
+S.unstreamList (stream (unstream (S.map safe (S.streamList a))))
+@
+
+`stream` and `unstream` surely cancel out via this rule:
+
+@
+{-# RULES "STREAM stream/unstream fusion" forall s. stream (unstream s) = s #-}
+@
+
+So we are left with
+
+@
+S.unstreamList (S.map safe (S.streamList a))
+@
+
+Now, what's this 'safe' function? Turns out it's defined as
+
+@
+safe :: Char -> Char
+safe c
+    | ord c .&. 0x1ff800 /= 0xd800 = c
+    | otherwise                    = '\xfffd'
+@
+
+Aha, so it's mapping some codepoints to @'\xfffd'@!
+There's a comment on top of it to explain this:
+
+```
+-- Unicode 'Data.Char.Surrogate' code points are not included in the set of Unicode
+-- scalar values, but are unfortunately admitted as valid 'Char'
+-- values by Haskell.  They cannot be represented in a 'Text'.  This
+-- function remaps those code points to the Unicode replacement
+-- character (U+FFFD, \'&#xfffd;\'), and leaves other code points
+-- unchanged.
+```
+
+This logic is lost with the mentioned rewrite rule.
+Not a huge loss, but it does mean that this rewrite rule isn't meaning preserving.
+
+
+We hope that in most cases it's fine.
+And if it's not, one can mark his function using either @pack@ or @unpack@
+with @NOINLINE@ pragma to prevent the rule from firing.
+
+So, eventually, we add the following rule:
+-}
+{-# RULES "pack/unpack internal" [1]
+    forall s. TF.unstreamList (TF.map T.safe (TF.streamList s)) = s
+#-}
+#endif
+
+{- In case if GHC didn't manage to inline and rewrite everything in
+the remaining phases (@Data.Text.pack@ is inlined at 1-st phase),
+we still have "pack/unpack" rule. Hopefully, one of them will fire.
+-}
+
+{- The opposite rule is safe to have because 'T.safe' /is/ the identity
+function for strings made up from valid characters, and 'Text' is guaranteed
+to have only valid ones.
+However, for this case there is no @unstream (stream s) = id@ rule,
+so we don't delve deep into internals. As long as @stream@ and @unstream@
+only perform conversion between text and stream of characters, they should
+be safe to collapse.
+-}
+{-# RULES "unpack/pack" [~0]
+    forall s. T.pack (T.unpack s) = s
+#-}
+
 -- | Polymorhpic version of 'Text.Read.readEither'.
 --
 -- >>> readEither @Text @Int "123"
 -- Right 123
 -- >>> readEither @Text @Int "aa"
 -- Left "Prelude.read: no parse"
+--
+-- @since 1.8.0
 readEither :: (ToString a, Read b) => a -> Either Text b
 readEither = first toText . Text.Read.readEither . toString
+
+-- | Polymorhpic version of 'Text.Read.readMaybe'.
+--
+-- >>> readMaybe @Int @Text "123"
+-- Just 123
+-- >>> readMaybe @Int @Text "aa"
+-- Nothing
+readMaybe :: forall b a. (ToString a, Read b) => a -> Maybe b
+readMaybe = Text.Read.readMaybe . toString
 
 -- | Generalized version of 'Prelude.show'.
 show :: forall b a . (Show.Show a, IsString b) => a -> b
diff --git a/src/Universum/String/Reexport.hs b/src/Universum/String/Reexport.hs
--- a/src/Universum/String/Reexport.hs
+++ b/src/Universum/String/Reexport.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE Safe #-}
+
 -- | This module reexports functions to work with 'Text' and 'ByteString' types.
 
 module Universum.String.Reexport
@@ -22,4 +24,4 @@
 import Data.Text.Encoding.Error (OnDecodeError, OnError, UnicodeException, lenientDecode,
                                  strictDecode)
 import Data.Text.Lazy (fromStrict, toStrict)
-import Text.Read (Read, readMaybe, reads)
+import Text.Read (Read, reads)
diff --git a/src/Universum/TypeOps.hs b/src/Universum/TypeOps.hs
--- a/src/Universum/TypeOps.hs
+++ b/src/Universum/TypeOps.hs
@@ -1,18 +1,13 @@
-{-# LANGUAGE CPP                #-}
-{-# LANGUAGE ConstraintKinds    #-}
-{-# LANGUAGE DataKinds          #-}
-{-# LANGUAGE ExplicitNamespaces #-}
-{-# LANGUAGE KindSignatures     #-}
-{-# LANGUAGE NoImplicitPrelude  #-}
-{-# LANGUAGE PolyKinds          #-}
-{-# LANGUAGE TypeFamilies       #-}
-{-# LANGUAGE TypeOperators      #-}
-
-#if __GLASGOW_HASKELL__ <= 710
-{-# LANGUAGE Trustworthy        #-}
-#else
-{-# LANGUAGE Safe               #-}
-#endif
+{-# LANGUAGE ConstraintKinds          #-}
+{-# LANGUAGE DataKinds                #-}
+{-# LANGUAGE ExplicitNamespaces       #-}
+{-# LANGUAGE KindSignatures           #-}
+{-# LANGUAGE NoImplicitPrelude        #-}
+{-# LANGUAGE PolyKinds                #-}
+{-# LANGUAGE Safe                     #-}
+{-# LANGUAGE TypeFamilies             #-}
+{-# LANGUAGE StandaloneKindSignatures #-}
+{-# LANGUAGE TypeOperators            #-}
 
 -- | Type operators for writing convenient type signatures.
 
@@ -22,14 +17,31 @@
        , type ($)
        ) where
 
-#if __GLASGOW_HASKELL__ <= 710
-import GHC.Prim (Constraint)
-#else
 import Data.Kind (Constraint)
-#endif
 
-import Control.Type.Operator (type ($), type (<+>))
+-- | Infix application.
+--
+-- @
+-- f :: Either String $ Maybe Int
+-- =
+-- f :: Either String (Maybe Int)
+-- @
+type ($) :: (k2 -> k1) -> k2 -> k1
+type f $ a = f a
+infixr 2 $
 
+-- | Map several constraints over a single variable.
+--
+-- @
+-- a :: [Show, Read] \<+> a => a -> a
+-- =
+-- a :: (Show a, Read a) => a -> a
+-- @
+type family (<+>) (c :: [k -> Constraint]) (a :: k) where
+    (<+>) '[] _ = (() :: Constraint)
+    (<+>) (ch ': ct) a = (ch a, (<+>) ct a)
+infixl 9 <+>
+
 -- | Map several constraints over several variables.
 --
 -- @
@@ -45,7 +57,7 @@
 -- f :: Each '[Show] [a, b] => a -> b -> String
 -- @
 type family Each (c :: [k -> Constraint]) (as :: [k]) where
-    Each c '[] = (() :: Constraint)
+    Each _ '[] = (() :: Constraint)
     Each c (h ': t) = (c <+> h, Each c t)
 
 -- | Map several constraints over a single variable.
@@ -56,4 +68,5 @@
 -- =
 -- a :: (Show a, Read a) => a -> a
 -- @
+type With :: [k -> Constraint] -> k -> Constraint
 type With a b = a <+> b
diff --git a/src/Universum/Unsafe.hs b/src/Universum/Unsafe.hs
--- a/src/Universum/Unsafe.hs
+++ b/src/Universum/Unsafe.hs
@@ -22,15 +22,19 @@
        , at
        , (!!)
        , fromJust
+       , foldr1
+       , foldl1
+       , minimum
+       , maximum
+       , minimumBy
+       , maximumBy
        ) where
 
-import Data.List (head, init, last, tail, (!!))
+import Data.List (foldl1, foldr1, head, init, last, maximum, maximumBy, minimum, minimumBy, tail,
+                  (!!))
 import Data.Maybe (fromJust)
 
 import Universum.Base (Int)
-
--- Non empty list definition for @liquidhaskell@.
-{-@ type NonEmptyList a = {xs : [a] | len xs > 0} @-}
 
 -- | Similar to '!!' but with flipped arguments.
 {-@ at :: n : Nat -> {xs : NonEmptyList a | len xs > n} -> a @-}
diff --git a/src/Universum/VarArg.hs b/src/Universum/VarArg.hs
--- a/src/Universum/VarArg.hs
+++ b/src/Universum/VarArg.hs
@@ -1,8 +1,11 @@
+{-# LANGUAGE CPP                    #-}
+{-# LANGUAGE FlexibleContexts       #-}
 {-# LANGUAGE FlexibleInstances      #-}
 {-# LANGUAGE FunctionalDependencies #-}
-{-# LANGUAGE IncoherentInstances    #-}
 {-# LANGUAGE MultiParamTypeClasses  #-}
+{-# LANGUAGE Safe                   #-}
 {-# LANGUAGE TypeFamilies           #-}
+{-# LANGUAGE TypeOperators          #-}
 {-# LANGUAGE UndecidableInstances   #-}
 
 -- | Provides operator of variable-arguments function composition.
@@ -11,6 +14,10 @@
        ( SuperComposition(..)
        ) where
 
+#if MIN_VERSION_base(4,17,0)
+import Data.Type.Equality (type (~))
+#endif
+
 -- $setup
 -- >>> import Universum.Base ((+))
 -- >>> import Universum.Container (null)
@@ -48,12 +55,12 @@
 
 infixl 8 ...
 
-instance (a ~ c, r ~ b) =>
+instance {-# INCOHERENT #-} (a ~ c, r ~ b) =>
          SuperComposition (a -> b) c r where
     f ... g = f g
     {-# INLINE (...) #-}
 
-instance (SuperComposition (a -> b) d r1, r ~ (c -> r1)) =>
+instance {-# INCOHERENT #-} (SuperComposition (a -> b) d r1, r ~ (c -> r1)) =>
          SuperComposition (a -> b) (c -> d) r where
     (f ... g) c = f ... g c
     {-# INLINE (...) #-}
diff --git a/test/Doctest.hs b/test/Doctest.hs
--- a/test/Doctest.hs
+++ b/test/Doctest.hs
@@ -1,14 +1,5 @@
-{-# LANGUAGE CPP #-}
-
 module Main (main) where
 
-#if defined(mingw32_HOST_OS) || __GLASGOW_HASKELL__ < 802
-
-main :: IO ()
-main = return ()
-
-#else
-
 import System.FilePath.Glob (glob)
 import Test.DocTest (doctest)
 
@@ -16,5 +7,3 @@
 main = do
     sourceFiles <- glob "src/**/*.hs"
     doctest $ "-XNoImplicitPrelude" : sourceFiles
-
-#endif
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,9 +1,9 @@
-module Main where
-
-import Test.Tasty (defaultMain)
+module Main
+  ( main
+  ) where
 
-import Test.Universum.Property (hedgehogTestTree)
+import Test.Tasty
+import Tree (tests)
 
 main :: IO ()
-main = do
-    defaultMain hedgehogTestTree
+main = tests >>= defaultMain
diff --git a/test/Test/Universum/BoolMProps.hs b/test/Test/Universum/BoolMProps.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Universum/BoolMProps.hs
@@ -0,0 +1,22 @@
+module Test.Universum.BoolMProps
+  ( hprop_andM
+  , hprop_orM
+  ) where
+
+import Hedgehog (Gen, Property, forAll, property, (===))
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+import qualified Universum as U
+
+genBoolList :: Gen [U.Bool]
+genBoolList = Gen.list (Range.linear 0 1000) Gen.bool
+
+hprop_andM :: Property
+hprop_andM = property $ do
+    bs <- forAll genBoolList
+    U.andM (return <$> bs) === ((return $ U.and bs) :: U.Maybe U.Bool)
+
+hprop_orM :: Property
+hprop_orM = property $ do
+    bs <- forAll genBoolList
+    U.orM (return <$> bs) === ((return $ U.or bs) :: U.Maybe U.Bool)
diff --git a/test/Test/Universum/Issue208.hs b/test/Test/Universum/Issue208.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Universum/Issue208.hs
@@ -0,0 +1,23 @@
+{-# LANGUAGE
+    GeneralizedNewtypeDeriving
+  , UndecidableInstances
+  , FlexibleContexts
+  , GADTs
+  , DerivingStrategies
+  , CPP
+#-}
+module Test.Universum.Issue208
+  () where
+
+#if __GLASGOW_HASKELL__ >= 900
+
+import Universum (Container)
+
+-- In ghc-8.6.3 this code will produce a @redundant constraint@ warning.
+-- In ghc-9.0.2 and newer no warnings would be produced.
+-- Issue #208: https://github.com/serokell/universum/issues/208
+
+newtype Test = Test [Int]
+    deriving newtype (Container)
+
+#endif
diff --git a/test/Test/Universum/ListProps.hs b/test/Test/Universum/ListProps.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Universum/ListProps.hs
@@ -0,0 +1,35 @@
+module Test.Universum.ListProps
+  ( hprop_ordNubCorrect
+  , hprop_hashNubCorrect
+  , hprop_sortNubCorrect
+  , hprop_unstableNubCorrect
+  ) where
+
+import Data.List (nub)
+import Hedgehog (Gen, Property, forAll, property, (===))
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+import qualified Universum as U
+
+genIntList :: Gen [U.Int]
+genIntList = Gen.list (Range.linear 0 10000) Gen.enumBounded
+
+hprop_ordNubCorrect :: Property
+hprop_ordNubCorrect = property $ do
+    xs <- forAll genIntList
+    U.ordNub xs === nub xs
+
+hprop_hashNubCorrect :: Property
+hprop_hashNubCorrect = property $ do
+    xs <- forAll genIntList
+    U.hashNub xs === nub xs
+
+hprop_sortNubCorrect :: Property
+hprop_sortNubCorrect = property $ do
+    xs <- forAll genIntList
+    U.sortNub xs === (U.sort $ nub xs)
+
+hprop_unstableNubCorrect :: Property
+hprop_unstableNubCorrect = property $ do
+    xs <- forAll genIntList
+    (U.sort $ U.unstableNub xs) === (U.sort $ nub xs)
diff --git a/test/Test/Universum/Property.hs b/test/Test/Universum/Property.hs
deleted file mode 100644
--- a/test/Test/Universum/Property.hs
+++ /dev/null
@@ -1,129 +0,0 @@
-module Test.Universum.Property
-        ( hedgehogTestTree
-        ) where
-
-import Universum 
-
-import Data.List (nub)
-import Hedgehog (Property, Gen, MonadGen, forAll, property, assert, (===))
-import Test.Tasty (testGroup, TestTree)
-import Test.Tasty.Hedgehog
-
-import qualified Universum as U
-import qualified Hedgehog.Gen              as Gen
-import qualified Hedgehog.Range            as Range
-import qualified Data.ByteString           as B
-import qualified Data.ByteString.Lazy      as LB
-import qualified Data.Text                 as T
-import qualified Data.Text.Lazy            as LT
-
-
-
-hedgehogTestTree :: TestTree
-hedgehogTestTree = testGroup "Tests" [utfProps, listProps, boolMProps]
-
-utfProps :: TestTree
-utfProps = testGroup "utf8 conversion property tests" 
-    [ testProperty "String to ByteString invertible" prop_StringToBytes
-    , testProperty "Text to ByteString invertible" prop_TextToBytes
-    , testProperty "ByteString to Text or String invertible" prop_BytesTo
-    ]
-
-unicode' :: MonadGen m => m U.Char
-unicode' = do
-    a <- Gen.unicode
-    if U.elem a ['\65534', '\65535']
-    then unicode'
-    else return a
-
-utf8String :: Gen U.String
-utf8String = Gen.string (Range.linear 0 10000) unicode'
-
-utf8Text :: Gen T.Text
-utf8Text = Gen.text (Range.linear 0 10000) unicode'
-
-utf8Bytes :: Gen B.ByteString
-utf8Bytes = Gen.utf8 (Range.linear 0 10000) unicode'
-
--- "\65534" fails, but this is from BU.toString
--- > import qualified Data.ByteString.UTF8 as BU
--- > BU.toString (BU.fromString "\65534") == "\65533"
--- > True 
-
-prop_StringToBytes :: Property
-prop_StringToBytes = property $ do
-    str <- forAll utf8String
-    assert $ str == (decodeUtf8 (encodeUtf8 str :: B.ByteString))
-          && str == (decodeUtf8 (encodeUtf8 str :: LB.ByteString))
-
-
-prop_TextToBytes :: Property
-prop_TextToBytes = property $ do
-    txt <- forAll utf8Text
-    assert $ txt == (decodeUtf8 (encodeUtf8 txt :: B.ByteString))
-          && txt == (decodeUtf8 (encodeUtf8 txt :: LB.ByteString))
-
--- "\239\191\190" fails, but this is the same as "\65534" :: String
-prop_BytesTo :: Property
-prop_BytesTo = property $ do
-    utf <- forAll utf8Bytes
-    assert $ utf == (encodeUtf8 (decodeUtf8 utf :: U.String))
-          && utf == (encodeUtf8 (decodeUtf8 utf :: T.Text))
-          && utf == (encodeUtf8 (decodeUtf8 utf :: LT.Text))
-
--- ordNub
-
-listProps :: TestTree
-listProps = testGroup "list function property tests" 
-    [ testProperty "Hedgehog ordNub xs == nub xs" prop_ordNubCorrect
-    , testProperty "Hedgehog hashNub xs == nub xs" prop_hashNubCorrect
-    , testProperty "Hedgehog sortNub xs == sort $ nub xs" prop_sortNubCorrect
-    , testProperty "Hedgehog sort $ unstableNub xs == sort $ nub xs" prop_unstableNubCorrect
-    ]
-
-genIntList :: Gen [U.Int]
-genIntList = Gen.list (Range.linear 0 10000) Gen.enumBounded
-
-prop_ordNubCorrect :: Property
-prop_ordNubCorrect = property $ do
-    xs <- forAll genIntList
-    U.ordNub xs === nub xs
-
-prop_hashNubCorrect :: Property
-prop_hashNubCorrect = property $ do
-    xs <- forAll genIntList
-    U.hashNub xs === nub xs
-
-prop_sortNubCorrect :: Property
-prop_sortNubCorrect = property $ do
-    xs <- forAll genIntList
-    U.sortNub xs === (U.sort $ nub xs)
-
-prop_unstableNubCorrect :: Property
-prop_unstableNubCorrect = property $ do
-    xs <- forAll genIntList
-    (U.sort $ U.unstableNub xs) === (U.sort $ nub xs)
-
-
--- logicM
--- this section needs a little more thought
-
-genBoolList :: Gen [U.Bool]
-genBoolList = Gen.list (Range.linear 0 1000) Gen.bool
-
-boolMProps :: TestTree
-boolMProps = testGroup "lifted logic function property tests"
-    [ testProperty "Hedgehog andM" prop_andM
-    , testProperty "Hedgehog orM" prop_orM
-    ]
-
-prop_andM :: Property
-prop_andM = property $ do
-    bs <- forAll genBoolList
-    U.andM (return <$> bs) === ((return $ U.and bs) :: U.Maybe U.Bool)
-
-prop_orM :: Property
-prop_orM = property $ do
-    bs <- forAll genBoolList
-    U.orM (return <$> bs) === ((return $ U.or bs) :: U.Maybe U.Bool)
-
diff --git a/test/Test/Universum/StringProps.hs b/test/Test/Universum/StringProps.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Universum/StringProps.hs
@@ -0,0 +1,55 @@
+-- Some tests in this module have a caveat: they depend on compiler
+-- optimizations and will likely fail if compiled with -O0.
+-- That's because they depend on rewrite rules (and test these rules),
+-- and these rules are usually not triggered without certain optimizations.
+
+module Test.Universum.StringProps
+  ( hprop_StringToTextAndBack
+  , hprop_StringToTextAndBackSurrogate
+  , hprop_TextToStringAndBack
+  ) where
+
+import Universum
+
+import qualified Data.Text as T
+import Hedgehog (Gen, Property, forAll, property, (===))
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+import qualified Universum as U
+
+unicodeAllString :: Gen U.String
+unicodeAllString = Gen.string (Range.linear 0 10000) Gen.unicodeAll
+
+unicodeAllText :: Gen T.Text
+unicodeAllText = Gen.text (Range.linear 0 10000) Gen.unicodeAll
+
+-- "\65534" fails, but this is from BU.toString
+-- > import qualified Data.ByteString.UTF8 as BU
+-- > BU.toString (BU.fromString "\65534") == "\65533"
+-- > True
+
+hprop_StringToTextAndBack :: Property
+hprop_StringToTextAndBack = property $ do
+  str <- forAll unicodeAllString
+  toString (toText str) === str
+
+-- |
+-- While 'String' may contain surrogate UTF-16 code points, actually UTF-8
+-- doesn't allow them, as well as 'Text'. 'Data.Text.pack' replaces invalid
+-- characters with unicode replacement character, so by default
+-- @toString . toText@ is not identity.
+--
+-- However, we have a rewrite rule by which we /replace/ @toString . toText@
+-- occurrences with the identity function.
+hprop_StringToTextAndBackSurrogate :: Property
+hprop_StringToTextAndBackSurrogate = property $ do
+  -- Surrogate character like this one should remain intact
+  -- Without the rewrite rule this string would be transformed to some other,
+  -- valid string
+  let str = "\xD800"
+  toString (toText str) === str
+
+hprop_TextToStringAndBack :: Property
+hprop_TextToStringAndBack = property $ do
+  txt <- forAll unicodeAllText
+  toText (toString txt) === txt
diff --git a/test/Test/Universum/UtfProps.hs b/test/Test/Universum/UtfProps.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Universum/UtfProps.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE CPP          #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Test.Universum.UtfProps
+  ( hprop_StringToBytes
+  , hprop_TextToBytes
+  , hprop_BytesTo
+  ) where
+
+import Universum
+
+import Hedgehog (Gen, MonadGen, Property, assert, forAll, property)
+#if MIN_VERSION_hedgehog(1,0,0)
+import Hedgehog (GenBase)
+#endif
+
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as LB
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as LT
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+import qualified Universum as U
+
+#if MIN_VERSION_hedgehog(1,0,0)
+unicode' :: (MonadGen m, GenBase m ~ Identity) => m U.Char
+#else
+unicode' :: MonadGen m => m U.Char
+#endif
+unicode' = do
+    a <- Gen.unicode
+    if U.elem a ['\65534', '\65535']
+    then unicode'
+    else return a
+
+utf8String :: Gen U.String
+utf8String = Gen.string (Range.linear 0 10000) unicode'
+
+utf8Text :: Gen T.Text
+utf8Text = Gen.text (Range.linear 0 10000) unicode'
+
+utf8Bytes :: Gen B.ByteString
+utf8Bytes = Gen.utf8 (Range.linear 0 10000) unicode'
+
+hprop_StringToBytes :: Property
+hprop_StringToBytes = property $ do
+    str <- forAll utf8String
+    assert $ str == (decodeUtf8 (encodeUtf8 str :: B.ByteString))
+          && str == (decodeUtf8 (encodeUtf8 str :: LB.ByteString))
+
+
+hprop_TextToBytes :: Property
+hprop_TextToBytes = property $ do
+    txt <- forAll utf8Text
+    assert $ txt == (decodeUtf8 (encodeUtf8 txt :: B.ByteString))
+          && txt == (decodeUtf8 (encodeUtf8 txt :: LB.ByteString))
+
+-- "\239\191\190" fails, but this is the same as "\65534" :: String
+hprop_BytesTo :: Property
+hprop_BytesTo = property $ do
+    utf <- forAll utf8Bytes
+    assert $ utf == (encodeUtf8 (decodeUtf8 utf :: U.String))
+          && utf == (encodeUtf8 (decodeUtf8 utf :: T.Text))
+          && utf == (encodeUtf8 (decodeUtf8 utf :: LT.Text))
diff --git a/test/Tree.hs b/test/Tree.hs
new file mode 100644
--- /dev/null
+++ b/test/Tree.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --tree-display -optF --generated-module -optF Tree #-}
diff --git a/universum.cabal b/universum.cabal
--- a/universum.cabal
+++ b/universum.cabal
@@ -1,5 +1,6 @@
+cabal-version:       2.2
 name:                universum
-version:             1.2.0
+version:             1.8.3.1
 synopsis:            Custom prelude used in Serokell
 description:         See README.md file for more details.
 homepage:            https://github.com/serokell/universum
@@ -11,21 +12,52 @@
 category:            Prelude
 stability:           stable
 build-type:          Simple
-cabal-version:       2.0
 bug-reports:         https://github.com/serokell/universum/issues
-tested-with:         GHC == 7.10.3
-                   , GHC == 8.0.2
-                   , GHC == 8.2.2
-                   , GHC == 8.4.2
+tested-with:         GHC == 8.10.7
+                   , GHC == 9.0.2
+                   , GHC == 9.2.8
+                   , GHC == 9.4.8
+                   , GHC == 9.6.7
+                   , GHC == 9.8.4
+                   , GHC == 9.10.3
+                   , GHC == 9.12.2
+                   , GHC == 9.14.1
 extra-doc-files:     CHANGES.md
                    , CONTRIBUTING.md
                    , README.md
 
 source-repository head
   type:     git
-  location: git@github.com:serokell/universum.git
+  location: https://github.com/serokell/universum.git
 
+common common-options
+  build-depends:       base >= 4.8 && < 5
+  ghc-options:
+                       -- Source: https://medium.com/mercury-bank/enable-all-the-warnings-a0517bc081c3
+                       -Weverything
+                       -Wno-missing-exported-signatures
+                       -Wno-missing-import-lists
+                       -Wno-missed-specialisations
+                       -Wno-all-missed-specialisations
+                       -Wno-unsafe
+                       -Wno-safe
+                       -Wno-missing-local-signatures
+                       -Wno-monomorphism-restriction
+                       -Wno-implicit-prelude
+                       -Wno-prepositive-qualified-module
+                       -Wno-inferred-safe-imports
+  if impl(ghc >= 9.2.0)
+    ghc-options:         -Wno-missing-kind-signatures
+
+  if impl(ghc >= 9.14.0)
+    ghc-options:         -Wno-pattern-namespace-specifier
+
+  default-language:    Haskell2010
+
+  default-extensions:  TypeOperators
+
 library
+  import:              common-options
   hs-source-dirs:      src
   exposed-modules:
                        Universum
@@ -49,7 +81,6 @@
                                Universum.Lifted.Env
                                Universum.Lifted.File
                                Universum.Lifted.IORef
-                               Universum.Lifted.ST
                            Universum.List
                                Universum.List.Reexport
                                Universum.List.Safe
@@ -62,6 +93,7 @@
                            Universum.Monoid
                            Universum.Nub
                            Universum.Print
+                               Universum.Print.Internal
                            Universum.String
                                Universum.String.Conversion
                                Universum.String.Reexport
@@ -69,74 +101,88 @@
                            Universum.Unsafe
                            Universum.VarArg
 
-  ghc-options:         -Wall -fwarn-implicit-prelude
-
-  build-depends:       base >= 4.8 && < 5
-                     , bytestring
+  build-depends:       bytestring
                      , containers
                      , deepseq
-                     , ghc-prim >= 0.4.0.0
                      , hashable
                      , microlens
                      , microlens-mtl
                      , mtl
                      , safe-exceptions
                      , stm
-                     , text
+                     , text >= 1.0.0.0 && <= 2.2
                      , transformers
-                     , type-operators
                      , unordered-containers
                      , utf8-string
                      , vector
 
-  default-language:    Haskell2010
+  ghc-options:         -Wimplicit-prelude
   default-extensions:  NoImplicitPrelude
                        OverloadedStrings
 
 test-suite universum-test
+  import:              common-options
   type:                exitcode-stdio-1.0
   hs-source-dirs:      test
   main-is:             Spec.hs
 
-  other-modules:       Test.Universum.Property
+  other-modules:
+                       Tree
+                       Test.Universum.Issue208
+                       Test.Universum.StringProps
+                       Test.Universum.UtfProps
+                       Test.Universum.ListProps
+                       Test.Universum.BoolMProps
 
-  build-depends:       base             >= 4.8 && < 5
-                     , universum
+  build-depends:       universum
                      , bytestring
                      , text
-                     , utf8-string
                      , hedgehog
                      , tasty
                      , tasty-hedgehog
+  build-tool-depends:
+      tasty-discover:tasty-discover
 
-  ghc-options:         -Wall -threaded
-  default-language:    Haskell2010
+  ghc-options:         -Wno-missing-safe-haskell-mode
+  ghc-options:         -threaded
 
 test-suite universum-doctest
+  import:              common-options
+  if os(windows)
+     buildable: False
+
   type:                exitcode-stdio-1.0
   hs-source-dirs:      test
   main-is:             Doctest.hs
 
-  build-depends:       base >= 4.8 && < 5
-                     , doctest
+  build-depends:       doctest
                      , Glob
 
+  ghc-options:         -Wno-missing-safe-haskell-mode
   ghc-options:         -threaded
-  default-language:    Haskell2010
+  -- https://github.com/sol/doctest/issues/327
+  -- TODO: re-enable when the issue is resolved
+  if impl(ghc >= 9.0.0)
+    buildable: False
 
 benchmark universum-benchmark
+  import:              common-options
   type:                exitcode-stdio-1.0
-  default-language:    Haskell2010
-  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+
   hs-source-dirs:      benchmark
   main-is:             Main.hs
-  build-depends:       base < 5
-                     , universum
+  build-depends:       universum
                      , containers
                      , gauge
+                     , text
                      , unordered-containers
-  if impl(ghc == 7.10.3)
-     build-depends:   semigroups >= 0.18
+
+  ghc-options:         -Wno-missing-safe-haskell-mode
+  if impl(ghc >= 9.8.0)
+    ghc-options:         -Wno-x-partial
+  -- TODO (#298): migrate from gauge to something
+  buildable: False
 
   default-extensions:  NoImplicitPrelude
                        ScopedTypeVariables
