Skip to content

Latest commit

 

History

History
263 lines (198 loc) · 11.9 KB

README.md

File metadata and controls

263 lines (198 loc) · 11.9 KB

Universum

Build Status Hackage

Custom Prelude used in Serokell.

What is this?

This README is the introduction to Universum and tutorial how to use it.

Structure of this tutorial

This tutorial has several parts:

  1. Philosophy and motivation.
  2. How to use it.
  3. What we've changed in Prelude (some gotchas).
  4. What already known things but that are not in Prelude we've brought into scope.
  5. What new thing we've added.

This is not a tutorial on Haskell or not even tutorial on each function. For detailed documentation of every function with examples and usages see Haddock documentation.

Why another custom Prelude?

Motivation

In Serokell we want to be as much productive as possible. That's why we are using Haskell. This choice implies that we're restricted to use Prelude: impicit import of basic functions, type classes and data type. But default Prelude is considered to be not so good due to some historical reasons.

So we decided to use better tool. Hopefully Haskell provides us with ability to replace default Prelude with some alternative. All we need to do is to implement new basic set of defaults. But we don't actually want to implement everything from scratch. There're plenty of already created custom preludes. After long and hot discussion our team decided to base our custom prelude on protolude. If you're not familiar with it you can read tutorial about protolude.

Next section describes why we made this choice and what we're willing to do. This tutorial doesn't cover differences from protolude. Intstead it says what differs from custom Prelude.

Main goals motivation

While creating and maintaining custom prelude we're following next goals:

  1. Avoid all partial functions. We like total and exception-free functions. You can still use some unsafe functions but they are not exported by default.
  2. Use more efficient string representations. String type is crushingly inefficient. All our functions tries to be polymorphic over string types or use Text as the default string type. Though String type alias is still reexported because community is evolving slowly, some libraries still use String type. But we recommend to avoid String!
  3. Don't reinvent wheels. We're not trying to rebuild whole type hierarchy from scratch as it's done in classy-prelude. Instead we just reexport common and well-known things from base and some other libraries used in everyday production programming in Haskell.
  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. Things like liftIO, ReaderT type, MVar-related functions have unambigous names, used in almost every non-trivial project and it's really tedious to import them manually every time.

Unlike protolude we're:

  1. Not trying to be as general as possible (thus we don't export much from GHC.Generics).
  2. Not trying to maintain every version of ghc compiler (only latest 3).
  3. But trying to be able to write production code easier (see enhancements and fixes).

How to use

Okay, enough philosophy. If you want to just start using universum and explore it with the help of compiler just set up according to the instructions bellow.

Disable the built-in prelude at the top of your file:

{-# LANGUAGE NoImplicitPrelude #-}

Or directly in your project .cabal file if you want to use in every module by default:

default-extensions: NoImplicitPrelude

Then in your modules add next import:

import Universum

If you're using Emacs, you can modify your configs a little bit if you don't want to write import Universum manually every time.

Gotchas

  • id is renamed to identity (because it's nice to be able to use id as a variable name).
  • head returns Maybe.
  • tail, last, init, (!!) are missing. Use tailMay/Def/Safe or import unsafe(Index|Head|Tail|Init|Last) from Unsafe if you need them.
  • undefined triggers a compiler warning, which is likely not what you want. Either use throwIO, Except, or error.
  • map is fmap now.
  • sortOn availaible without import. This function sorts efficiently a list based on some property of its elements (e.g. sortOn length would sort elements by length).
  • 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 putText in this case.
  • Since show doesn't come from Show anymore, you can't write Show instances easily. Either use autoderived instances or Buildable.
  • You can't call some Foldable methods over Maybe and some other types. Foldable generalization is useful but potentially error-prone. Instead we created our own fully compatible with Foldable Container type class but that restrics usages of functions like length over Maybe, Either, Identity and tuples. We're also using GHC 8 feature of custom compile-time errors to produce more helpful messages.
  • As a consequence of previous point, some functions like traverse_, forM_, sequenceA_, etc are generalized over Container and NonTrivialContainer type classes.
  • error takes Text.

Things that you were already using but now don't have to import explicitly

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 functions are generalised to MonadIO.

deepseq is exported. For instance, if you want to force deep evaluation of some value (in IO), you can write evaluate (force a). WHNF evaluation is possible with evaluate a.

Also we reexport big chunks of these libraries: mtl, stm, safe, microlens.

However, put and get (for MonadState) are clashing with Binary so they're not exported.

More precisely about functions from safe: safe variants of common list/Maybe functions from base.

  • (head|tail|last|at)May return Maybe instead of failing.
  • (head|init|last|at)Def let you specify a default value in case of failure.
  • (init|tail)Safe return an empty list in case of failure.

However, there's still pending issue about some enhancements.

Bifunctor 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, toS can convert any string type to any string type. But you can use toText, toByteString, toString functions to convert to any specific type.

Debugging and undefineds

trace, traceM, traceShow, etc are available by default. GHC will warn you if you leave them in code accidentally, however (same for undefined).

We also have data Undefined = Undefined (which, too, come with warnings).

What's new?

Finally, we can move to part describing what new cool features we bring with universum.

  • foreach is flip fmap.

  • uncons and unsnoc split a list at the first/last element.

  • ordNub is an O(n log n) version of nub (which is quadratic).

  • (&) – reverse application. x & f & g instead of g $ f $ x is useful sometimes.

  • pretty and prettyL for converting Buildable into Text (can be used instead of show).

  • 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 you expect.

  • 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. For example, before:

    case mbX of
        Nothing -> return ()
        Just x  -> ... x ...

    After:

    whenJust mbX $ \x ->
        ... x ...
  • for_ for loops. There's also forM_ but for_ looks a bit nicer.

    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:

    f :: Each [Show, Read] [a, b] => a -> b -> String

    translates into this:

    f :: (Show a, Show b, Read a, Show b) => a -> b -> String
  • 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 for creating singleton containers. Even monomorhpic like Text.

  • evaluateWHNF and evaluateNF functions as clearer and lifted aliases for evaluate and evaluate . force.

License

Released under the MIT License. Copyright (c) 2016, Stephen Diehl, 2016-2017, Serokell