Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/serokell/universum
:milky_way: Prelude written in @Serokell
https://github.com/serokell/universum
alternative-prelude base haskell library prelude
Last synced: 10 days ago
JSON representation
:milky_way: Prelude written in @Serokell
- Host: GitHub
- URL: https://github.com/serokell/universum
- Owner: serokell
- License: mit
- Created: 2016-07-25T10:55:02.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2024-10-16T17:54:10.000Z (2 months ago)
- Last Synced: 2024-12-07T05:03:26.474Z (18 days ago)
- Topics: alternative-prelude, base, haskell, library, prelude
- Language: Haskell
- Homepage:
- Size: 892 KB
- Stars: 177
- Watchers: 35
- Forks: 28
- Open Issues: 38
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGES.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
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)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)`universum` is a custom prelude used in @Serokell that has:
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),
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. Focus on safety, convenience and efficiency.What is this file about?
------------------------This README contains introduction to `Universum` and a tutorial on how to use it.
Structure of this tutorial
--------------------------This tutorial has several parts:
1. [Philosophy and motivation.](#why-another-custom-prelude-)
2. [How to use `universum`.](#how-to-use-universum-)
3. [Changes in `Prelude` (some gotchas).](#gotchas-)
4. [Already known things that weren't in `Prelude` brought into scope.](#things-that-you-were-already-using-but-now-you-dont-have-to-import-them-explicitly-)
5. [New things added.](#whats-new-)
6. [Migration guide from `Prelude`.](#migration-guide-from-prelude-)This is neither a tutorial on _Haskell_ nor tutorial on each function contained in Universum. For detailed
documentation of every function together with examples and usage, see
[_Haddock documentation_](http://hackage.haskell.org/package/universum).Why another custom Prelude? [↑](#structure-of-this-tutorial)
---------------------------### Motivation
At [Serokell](https://github.com/serokell/), we strive to be as productive as possible. That's why we are using [_Haskell_](https://haskell-lang.org/). This choice of language implies
that we're restricted to use [`Prelude`](http://hackage.haskell.org/package/base-4.9.1.0/docs/Prelude.html):
implicit import of basic functions, type classes and data types. Unfortunately, the default `Prelude`
[is considered to be not so good](https://news.ycombinator.com/item?id=8002749)
due to some historical reasons.This is why we decided to use a better tool. Luckily, _Haskell_ provides us with the ability
to replace default `Prelude` with an alternative. All we had to do is to implement a
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/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`.### Main goals
While creating and maintaining a custom prelude, we are pursuing the following goals:
1. Avoid all [partial functions](https://www.reddit.com/r/haskell/comments/5n51u3/why_are_partial_functions_as_in_head_tail_bad/).
We like [total](http://mathworld.wolfram.com/TotalFunction.html) and exception-free functions.
You can still use some unsafe functions from `Universum.Unsafe` module,
but they are not exported by default.
2. Use more efficient [string representations](https://www.reddit.com/r/haskell/comments/29jw0s/whats_wrong_with_string/).
`String` type is crushingly inefficient. All our functions either try to be polymorphic over string
type or use [`Text`](http://hackage.haskell.org/package/text-1.2.2.1/docs/Data-Text.html)
as the default string type. Because the community is evolving slowly, some libraries still use `String` type, so `String` type alias is still reexported. We recommend to avoid `String` as much as you can!
3. Try to not reinvent the wheel. We're not trying to rebuild whole type hierarchy from scratch,
as it's done in [`classy-prelude`](https://github.com/snoyberg/mono-traversable).
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)
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 (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)).How to use Universum [↑](#structure-of-this-tutorial)
--------------------Okay, enough philosophy. If you want to just start using `universum` and
explore it with the help of compiler, set everything up according to the instructions below.Disable the built-in prelude at the top of your file:
```haskell
{-# LANGUAGE NoImplicitPrelude #-}
```Or directly in your project `.cabal` file, if you want to use in every module by default:
```haskell
default-extensions: NoImplicitPrelude
```Then add the following import to your modules:
```haskell
import Universum
```If you're using [Emacs](https://www.gnu.org/software/emacs/) and don't want to
type `import Universum` manually every time, you can
[modify your configs](https://github.com/serokell/universum/issues/8#issuecomment-276444879)
a little bit.If you want to get familiar with `universum` internal structure, you can just
read top-level documentation for
[`Universum`](http://hackage.haskell.org/package/universum/docs/Universum.html)
module.Gotchas [↑](#structure-of-this-tutorial)
-------* `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:
+ `sortBy :: (a -> a -> Ordering) -> [a] -> [a]`: sorts list using given custom comparator.
+ `sortWith :: Ord b => (a -> b) -> [a] -> [a]`: sorts a list based on some property of its elements.
+ `sortOn :: Ord b => (a -> b) -> [a] -> [a]`: just like `sortWith`, but more time-efficient if function is calculated slowly (though less space-efficient). So you should write `sortOn length` (would sort elements by length) but `sortWith fst` (would sort list of pairs by first element).
* Functions `sum` and `product` are strict now, which makes them more efficient.
* If you try to do something like `putStrLn "hi"`, you'll get an error message if
`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/).
Instead we created our own fully compatible with `Foldable`
[`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://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)
------------------------------------------------------------------------------------### Commonly used libraries
First of all, we reexport some generally useful modules: `Control.Applicative`,
`Data.Traversable`, `Data.Monoid`, `Control.DeepSeq`, `Data.List`, and lots of others.
Just remove unneeded imports after importing `Universum` (GHC should tell you which ones).Then, some commonly used types: `Map/HashMap/IntMap`, `Set/HashSet/IntSet`, `Seq`, `Text` and `ByteString`
(as well as synonyms `LText` and `LByteString` for lazy versions).`liftIO` and `MonadIO` are exported by default. A lot of `IO` functions are generalized to `MonadIO`.
`deepseq` is exported. For instance, if you want to force deep evaluation of some value (in IO),
you can write `evaluateNF a`. WHNF evaluation is possible with `evaluateWHNF a`.We also reexport big chunks of these libraries: `mtl`, `stm`, `microlens`, `microlens-mtl`.
[`Bifunctor`](http://hackage.haskell.org/package/base-4.9.1.0/docs/Data-Bifunctor.html)
type class with useful instances is exported.* `first` and `second` functions apply a function to first/second part of a tuple (for tuples).
* `bimap` takes two functions and applies them to first and second parts respectively.### Text
We export `Text` and `LText`, and some functions work with `Text` instead of `String` –
specifically, IO functions (`readFile`, `putStrLn`, etc) and `show`. In fact, `show`
is polymorphic and can produce strict or lazy `Text`, `String`, or `ByteString`.
Also, `toText/toLText/toString` can convert `Text|LText|String` types to `Text/LText/String`. If you want to convert to and from `ByteString` use `encodeUtf8/decodeUtf8` functions.### Debugging and `undefined`s
`trace`, `traceM`, `traceShow`, etc. are available by default. GHC will warn you
if you accidentally leave them in code, however (same for `undefined`).We also have `data Undefined = Undefined` (which, too, comes with warnings).
### Exceptions
We use [`safe-exceptions`](https://github.com/fpco/safe-exceptions)
library for exceptions handling. Don't import `Control.Exceptions`
module explicitly. Instead use functionality from `safe-exceptions`
provided by `universum` or import `Control.Exceptions.Safe` module.What's new? [↑](#structure-of-this-tutorial)
-----------Finally, we can move to part describing the new cool features we bring with `universum`.
* `uncons` splits a list at the first element.
* `ordNub` and `sortNub` are _O(n log n)_ versions of `nub` (which is quadratic)
and `hashNub` and `unstableNub` are almost _O(n)_ versions of `nub`.
* `(&)` – reverse application. `x & f & g` instead of `g $ f $ x` is useful sometimes.
* `whenM`, `unlessM`, `ifM`, `guardM` are available and do what you expect
them to do (e.g. `whenM (doesFileExist "foo")`).
* Very generalized version of `concatMapM`, too, is available and does what expected.
* `readMaybe` and `readEither` are like `read` but total and give either
`Maybe` or `Either` with parse error.
* `when(Just|Nothing|Left|Right|NotEmpty)[M][_]`
let you conditionally execute something. Before:```haskell
case mbX of
Nothing -> return ()
Just x -> ... x ...
```After:
```haskell
whenJust mbX $ \x ->
... x ...
```* `for_` for loops. There's also `forM_` but `for_` looks a bit nicer.
```haskell
for_ [1..10] $ \i -> do
...
```* `andM`, `allM`, `anyM`, `orM` are monadic version of corresponding functions from `base`.
* Type operator `$` for writing types like `Maybe $ Either String $ Maybe Int`.
* `Each` type family. So this:```haskell
f :: Each [Show, Read] [a, b] => a -> b -> String
```translates into this:
```haskell
f :: (Show a, Show b, Read a, Read b) => a -> b -> String
```* `With` type operator. So this:
```haskell
a :: With [Show, Read] a => a -> a
```translates into this:
```haskell
a :: (Show a, Read a) => a -> a
```* Variadic composition operator `(...)`. So you can write:
```haskell
ghci> (show ... (+)) 1 2
"3"
ghci> show ... 5
"5"
ghci> (null ... zip5) [1] [2] [3] [] [5]
True
ghci> let process = map (+3) ... filter
ghci> process even [1..5]
[5,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](/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`.
* `ToPairs` type class for data types that can be converted to list of pairs (like `Map` or `HashMap` or `IntMap`).Migration guide from Prelude [↑](#structure-of-this-tutorial)
----------------------------In order to replace default `Prelude` with `universum` you should start with instructions given in
[how to use universum](https://github.com/serokell/universum#how-to-use-universum-) section.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`, `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`.
+ `head <$> nonEmpty l` is `safeHead l`
+ `tail` is `drop 1`. It's almost never a good idea to use `tail` from `Prelude`.
3. Add `import qualified Universum.Unsafe as Unsafe` and replace function with qualified usage.
3. If you use `fromJust` or `!!` you should use them from `import qualified Universum.Unsafe as Unsafe`.
4. Derive or implement `Container` instances for your data types which implement
`Foldable` instances. This can be done in a single line because `Container`
type class automatically derives from `Foldable`.
5. `Container` type class from `universum` replaces `Foldable` and doesn't have
instances for `Maybe a`, `(a, b)`, `Identity a` and `Either a b`. If you use
`foldr` or `forM_` or similar for something like `Maybe a` you should replace
usages of such function with monomorhpic alternatives:
* `Maybe`
+ `(?:) :: Maybe a -> a -> a`
+ `fromMaybe :: a -> Maybe a -> a`
+ `maybeToList :: Maybe a -> [a]`
+ `maybeToMonoid :: Monoid m => Maybe m -> m`
+ `maybe :: b -> (a -> b) -> Maybe a -> b`
+ `whenJust :: Applicative f => Maybe a -> (a -> f ()) -> f ()`
+ `whenJustM :: Monad m => m (Maybe a) -> (a -> m ()) -> m ()`* `Either`
+ `fromLeft :: a -> Either a b -> a`
+ `fromRight :: b -> Either a b -> b`
+ `either :: (a -> c) -> (b -> c) -> Either a b -> c`
+ `whenRight :: Applicative f => Either l r -> (r -> f ()) -> f ()`
+ `whenRightM :: Monad m => m (Either l r) -> (r -> m ()) -> m ()`6. If you have types like `foo :: Foldable f => f a -> a -> a` you should chose one of the following:
+ `Right`: Modify types for `Container` like `foo :: (Container t, Element t ~ a) => t -> a -> a`.
+ `Left`: Import `Data.Foldable` module `qualified` and use everything `Foldable`-related qualified.
7. Forget about `String` type.
+ Replace `putStr` and `putStrLn` with `putText` and `putTextLn`.
+ Replace `(++)` with `(<>)` for `String`-like types.
+ Try to use [`fmt`](http://hackage.haskell.org/package/fmt) library if you need to construct messages.
+ 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.
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.