diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,119 @@
-Unreleased
+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
 =====
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -66,8 +66,8 @@
 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 deprecation of `x` which happens in a `breaking` release.
-The second step is deprecation of `x` which happens in another `breaking` release after deprecation of `x`.
+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.
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/gromakovsky/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)
@@ -16,9 +15,7 @@
    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,8 +76,7 @@
    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.
@@ -92,8 +87,7 @@
 
 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 (but [at least 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)).
 
@@ -134,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:
@@ -147,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/).
@@ -154,7 +149,7 @@
   [`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.
@@ -289,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`.
@@ -304,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`.
@@ -345,19 +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)
----------------------------
-
-Please submit a PR if you are using Universum!
-
-| λ |
-|---|
-| [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) |
-| [tintin](https://github.com/theam/tintin) |
-| [require](https://theam.github.io/require/) |
-| [ariadne](https://github.com/serokell/ariadne) |
+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/benchmark/Main.hs b/benchmark/Main.hs
--- a/benchmark/Main.hs
+++ b/benchmark/Main.hs
@@ -1,4 +1,6 @@
-module Main where
+module Main
+  ( main
+  ) where
 
 import Universum hiding (show)
 
@@ -70,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
@@ -108,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
@@ -177,11 +180,8 @@
 bgroupTextConversion :: Benchmark
 bgroupTextConversion =
   bgroup "text conversions"
-    [ let str = replicate 100000 'a'
-          countLength x = length (toString x)
-      in bench "toString . toText" $ whnf (countLength . toText) str
-
-    , let txt = T.replicate 100000 (T.singleton 'a')
-          countLength x = length (toText x)
-      in bench "toText . toString" $ whnf (countLength . toString) txt
+    [ -- 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
@@ -67,45 +61,32 @@
 
 -- 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, ($))
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 (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,7 +278,7 @@
     -- 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
 
     -- | Convert container to list of elements.
@@ -216,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 #-}
 
@@ -236,7 +319,7 @@
     {-# 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 #-}
 
@@ -249,16 +332,6 @@
     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 #-}
@@ -272,36 +345,6 @@
       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 :: Eq (Element t) => Element t -> t -> Bool
     notElem x = not . elem x
     {-# INLINE notElem #-}
@@ -328,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
 ----------------------------------------------------------------------------
@@ -344,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
@@ -377,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
@@ -411,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
@@ -446,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
@@ -485,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,7 +582,11 @@
     notElem = Set.notMember
     {-# INLINE notElem #-}
 
+#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 #-}
 
@@ -514,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)
@@ -523,7 +606,6 @@
 instance Container (Sum a)
 instance Container (NonEmpty a)
 instance Container (ZipList a)
-#endif
 
 -- Containers
 instance Container (HashMap k v)
@@ -548,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]
@@ -568,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]
@@ -592,7 +671,6 @@
 --           use
 --               maybeToMonoid :: Monoid m => Maybe m -> m
 -- ...
-#endif
 product :: (Container t, Num (Element t)) => t -> Element t
 product = foldl' (*) 1
 
@@ -682,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:"
@@ -701,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
@@ -742,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,15 +1,14 @@
 {-# 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
@@ -32,18 +31,14 @@
 import Control.Monad (Monad, return)
 import Data.Data (Data)
 import Data.Text (Text, pack, unpack)
-import Data.Typeable (Typeable)
 import GHC.Generics (Generic)
 import System.IO.Unsafe (unsafePerformIO)
 
-#if ( __GLASGOW_HASKELL__ >= 800 )
 import GHC.Exception (errorCallWithCallStackException)
 import GHC.Exts (RuntimeRep, TYPE, raise#)
 
-import Universum.Base (HasCallStack, callStack)
-#endif
-
 import Universum.Applicative (pass)
+import Universum.Base (HasCallStack, callStack)
 import Universum.Print (putStrLn)
 
 import qualified Prelude as P
@@ -56,14 +51,9 @@
     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
 error s = raise# (errorCallWithCallStackException (unpack s) callStack)
-#else
-error :: Text -> a
-error s = P.error (unpack s)
-#endif
 
 -- | Version of 'Debug.Trace.traceShow' that leaves a warning.
 {-# WARNING traceShow "'traceShow' remains in code" #-}
@@ -120,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 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,24 +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, 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)
@@ -36,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"
@@ -46,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:
 
@@ -86,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,7 +1,9 @@
+{-# LANGUAGE Safe #-}
+
 -- | This module reexports very basic and primitive functions and function combinators.
 
 module Universum.Function
        ( module Data.Function
        ) where
 
-import Data.Function (const, fix, flip, id, on, ($), (.))
+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/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/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.
@@ -22,7 +21,7 @@
 import System.IO (Handle, IOMode)
 
 import qualified Data.Text.IO as XIO
-import qualified System.IO as XIO (openFile, hClose, IO)
+import qualified System.IO as XIO (IO, hClose, openFile)
 
 ----------------------------------------------------------------------------
 -- Text
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,27 +1,29 @@
-{-# LANGUAGE CPP  #-}
 {-# LANGUAGE Safe #-}
 
 -- | This module contains safe functions to work with list type (mostly with 'NonEmpty').
 
 module Universum.List.Safe
        ( uncons
-#if ( __GLASGOW_HASKELL__ >= 800 )
        , 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.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 (($))
@@ -39,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)
@@ -56,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,3 +1,6 @@
+{-# LANGUAGE Safe #-}
+{-# LANGUAGE CPP #-}
+
 {-| Functions to remove duplicates from a list.
 
  = Performance
@@ -28,29 +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 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.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.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]
@@ -68,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 _ []     = []
@@ -81,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
 
@@ -89,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/Internal.hs b/src/Universum/Print/Internal.hs
--- a/src/Universum/Print/Internal.hs
+++ b/src/Universum/Print/Internal.hs
@@ -12,9 +12,11 @@
 __even for minor version increments__.
 -}
 {-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE Trustworthy       #-}
+
 module Universum.Print.Internal (Print(..)) where
 
-import qualified System.IO as SIO (hPutStr, hPutStrLn, Handle)
+import qualified System.IO as SIO (Handle, hPutStr, hPutStrLn)
 
 import qualified Data.ByteString.Char8 as BS
 import qualified Data.ByteString.Lazy.Char8 as BL
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,7 @@
 {-# 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
@@ -24,15 +24,20 @@
 
          -- * 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)
@@ -46,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))
 
@@ -169,17 +174,24 @@
 there are still some libraries which use 'String's and thus make us perform
 conversions back and forth.
 
-Note that @toString . toText@ is not strictly equal to identity function, see
-explanation in the comment below.
+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
 #-}
 
-{- [Note toString-toText-rewritting]
+-- 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 if take rules defined in 'Data.Text' into account.
+We can do even better than above if take rules defined in 'Data.Text' into
+account.
 
 Quoting investigation of @int-index:
 
@@ -230,7 +242,7 @@
 There's a comment on top of it to explain this:
 
 ```
--- UTF-16 surrogate code points are not included in the set of Unicode
+-- 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
@@ -251,6 +263,7 @@
 {-# 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),
@@ -258,7 +271,7 @@
 -}
 
 {- 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
+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@
@@ -275,8 +288,19 @@
 -- 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,11 +17,7 @@
        , type ($)
        ) where
 
-#if __GLASGOW_HASKELL__ <= 710
-import GHC.Prim (Constraint)
-#else
 import Data.Kind (Constraint)
-#endif
 
 -- | Infix application.
 --
@@ -35,6 +26,7 @@
 -- =
 -- f :: Either String (Maybe Int)
 -- @
+type ($) :: (k2 -> k1) -> k2 -> k1
 type f $ a = f a
 infixr 2 $
 
@@ -46,7 +38,7 @@
 -- a :: (Show a, Read a) => a -> a
 -- @
 type family (<+>) (c :: [k -> Constraint]) (a :: k) where
-    (<+>) '[] a = (() :: Constraint)
+    (<+>) '[] _ = (() :: Constraint)
     (<+>) (ch ': ct) a = (ch a, (<+>) ct a)
 infixl 9 <+>
 
@@ -65,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.
@@ -76,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,177 +0,0 @@
-{-# LANGUAGE CPP          #-}
-{-# LANGUAGE TypeFamilies #-}
-
-module Test.Universum.Property
-        ( hedgehogTestTree
-        ) where
-
-import Universum
-
-import Data.List (nub)
-import Hedgehog (Gen, MonadGen, Property, assert, forAll, property, (===))
-#if MIN_VERSION_hedgehog(1,0,0)
-import Hedgehog (GenBase)
-#endif
-import Test.Tasty (TestTree, testGroup)
-import Test.Tasty.Hedgehog
-
-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
-
-hedgehogTestTree :: TestTree
-hedgehogTestTree = testGroup "Tests" [stringProps, utfProps, listProps, boolMProps]
-
-stringProps :: TestTree
-stringProps = testGroup "String conversions"
-    [ testProperty "toString . toText = id" prop_StringToTextAndBack
-    , testProperty "`toString . toText` for UTF-16 surrogate"
-        prop_StringToTextAndBackSurrogate
-    , testProperty "toText . toString = id" prop_TextToStringAndBack
-    ]
-
-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
-    ]
-
-#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'
-
-unicodeAllString :: Gen U.String
-unicodeAllString = Gen.string (Range.linear 0 10000) Gen.unicodeAll
-
-utf8Text :: Gen T.Text
-utf8Text = Gen.text (Range.linear 0 10000) unicode'
-
-unicodeAllText :: Gen T.Text
-unicodeAllText = Gen.text (Range.linear 0 10000) Gen.unicodeAll
-
-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_StringToTextAndBack :: Property
-prop_StringToTextAndBack = property $ do
-  str <- forAll unicodeAllString
-  toString (toText str) === str
-
--- | See comment to this function:
--- <http://hackage.haskell.org/package/text-1.2.3.1/docs/src/Data.Text.Internal.html#safe>
---
--- 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.
-prop_StringToTextAndBackSurrogate :: Property
-prop_StringToTextAndBackSurrogate = property $ do
-  -- Surrogate character like this one should remain intact
-  -- Without rewrite rule this string would be transformed to "\9435"
-  let str = "\xD800"
-  toString (toText str) === str
-
-prop_TextToStringAndBack :: Property
-prop_TextToStringAndBack = property $ do
-  txt <- forAll unicodeAllText
-  toText (toString txt) === txt
-
-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.6.1
+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,22 +12,52 @@
 category:            Prelude
 stability:           stable
 build-type:          Simple
-cabal-version:       >=1.18
 bug-reports:         https://github.com/serokell/universum/issues
-tested-with:         GHC == 8.0.2
-                   , GHC == 8.2.2
-                   , GHC == 8.4.3
-                   , GHC == 8.6.5
-                   , GHC == 8.8.1
+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
@@ -70,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
-                     -- Make sure that "toString-toText-rewritting" note
-                     -- is still valid when bumping this constraint.
-                     , text >= 1.0.0.0 && <= 1.2.4.0
+                     , text >= 1.0.0.0 && <= 2.2
                      , transformers
                      , 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
+
+  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
