# rank2classes

standard type constructor class hierarchy, only with methods of rank 2 types https://github.com/blamario/grampa/tree/master/rank2classes

LTS Haskell 14.2: | 1.3 |

Stackage Nightly 2019-08-24: | 1.3 |

Latest on Hackage: | 1.3 |

**Mario Blažević**

**Mario Blažević**

#### Module documentation for 1.3

`rank2classes-1.3@sha256:c34b7719ba43a7e8332e3932b472eb386c9e0617f7dcd49f8e88c52ba29cf76d,2241`

# Rank 2 Classes

### The standard constructor type classes in the parallel rank-2 universe

The rank2 package exports module `Rank2`

, meant to be imported qualified like this:

```
{-# LANGUAGE RankNTypes, TemplateHaskell, TypeOperators #-}
module MyModule where
import qualified Rank2
import qualified Rank2.TH
```

Several more imports for the examples…

```
import Data.Functor.Classes (Show1, showsPrec1)
import Data.Functor.Identity (Identity(..))
import Data.Functor.Const (Const(..))
import Data.List (find)
```

The `Rank2`

import will make available the following type classes:

The methods of these type classes all have rank-2 types. The class instances are data types of kind `(k -> *) -> *`

,
one example of which would be a database record with different field types but all wrapped by the same type
constructor:

```
data Person f = Person{
name :: f String,
age :: f Int,
mother, father :: f (Maybe PersonVerified)
}
```

By wrapping each field we have declared a generalized record type. It can made to play different roles by switching the
value of the parameter `f`

. Some examples would be

```
type PersonVerified = Person Identity
type PersonText = Person (Const String)
type PersonWithErrors = Person (Either String)
type PersonDatabase = [PersonVerified]
type PersonDatabaseByColumns = Person []
```

If you wish to have the standard Eq and
Show instances for a record type like `Person`

,
it’s best if they refer to the
Eq1 and
Show1 instances for its
parameter `f`

:

```
instance Show1 f => Show (Person f) where
showsPrec prec person rest =
"Person{" ++ separator ++ "name=" ++ showsPrec1 prec' (name person)
("," ++ separator ++ "age=" ++ showsPrec1 prec' (age person)
("," ++ separator ++ "mother=" ++ showsPrec1 prec' (mother person)
("," ++ separator ++ "father=" ++ showsPrec1 prec' (father person)
("}" ++ rest))))
where prec' = succ prec
separator = "\n" ++ replicate prec' ' '
```

You can create the rank-2 class instances for your data types manually, or you can generate the instances using the
templates imported from the `Rank2.TH`

module with a single line of code per data type:

```
$(Rank2.TH.deriveAll ''Person)
```

Either way, once you have the rank-2 type class instances, you can use them to easily convert between records with
different parameters `f`

.

### Record construction and modification examples

In case of our `Person`

record, a couple of helper functions will prove handy:

```
findPerson :: PersonDatabase -> String -> Maybe PersonVerified
findPerson db nameToFind = find ((nameToFind ==) . runIdentity . name) db
personByName :: PersonDatabase -> String -> Either String (Maybe PersonVerified)
personByName db personName
| null personName = Right Nothing
| p@Just{} <- findPerson db personName = Right p
| otherwise = Left ("Nobody by name of " ++ personName)
```

Now we can start by constructing a `Person`

record with rank-2 functions for fields. This record is not so much a person
as a field-by-field person verifier:

```
personChecker :: PersonDatabase -> Person (Const String Rank2.~> Either String)
personChecker db =
Person{name= Rank2.Arrow (Right . getConst),
age= Rank2.Arrow $ \(Const age)->
case reads age
of [(n, "")] -> Right n
_ -> Left (age ++ " is not an integer"),
mother= Rank2.Arrow (personByName db . getConst),
father= Rank2.Arrow (personByName db . getConst)}
```

We can apply it using the Rank2.<*>
method of the Rank2.Apply type class to a bunch
of textual fields for `Person`

, and get back either errors or proper field values:

```
verify :: PersonDatabase -> PersonText -> PersonWithErrors
verify db person = personChecker db Rank2.<*> person
```

If there are no errors, we can get a fully verified record by applying Rank2.traverse to the result:

```
completeVerified :: PersonWithErrors -> Either String PersonVerified
completeVerified = Rank2.traverse (Identity <$>)
```

or we can go in the opposite direction with Rank2.<$>:

```
uncompleteVerified :: PersonVerified -> PersonWithErrors
uncompleteVerified = Rank2.fmap (Right . runIdentity)
```

If on the other hand there *are* errors, we can collect them using
Rank2.foldMap:

```
verificationErrors :: PersonWithErrors -> [String]
verificationErrors = Rank2.foldMap (either (:[]) (const []))
```

Here is an example GHCi session:

```
-- |
-- >>> :{
--let Right alice = completeVerified $
-- verify [] Person{name= Const "Alice", age= Const "44",
-- mother= Const "", father= Const ""}
-- Right bob = completeVerified $
-- verify [] Person{name= Const "Bob", age= Const "45",
-- mother= Const "", father= Const ""}
-- Right charlie = completeVerified $
-- verify [alice, bob] Person{name= Const "Charlie", age= Const "19",
-- mother= Const "Alice", father= Const "Bob"}
-- :}
--
-- >>> charlie
-- Person{
-- name=Identity "Charlie",
-- age=Identity 19,
-- mother=Identity (Just Person{
-- name=(Identity "Alice"),
-- age=(Identity 44),
-- mother=(Identity Nothing),
-- father=(Identity Nothing)}),
-- father=Identity (Just Person{
-- name=(Identity "Bob"),
-- age=(Identity 45),
-- mother=(Identity Nothing),
-- father=(Identity Nothing)})}
-- >>> :{
--let dave = verify [alice, bob, charlie]
-- Person{name= Const "Dave", age= Const "young",
-- mother= Const "Lise", father= Const "Mike"}
-- :}
--
-- >>> dave
-- Person{
-- name=Right "Dave",
-- age=Left "young is not an integer",
-- mother=Left "Nobody by name of Lise",
-- father=Left "Nobody by name of Mike"}
-- >>> completeVerified dave
-- Left "young is not an integer"
-- >>> verificationErrors dave
-- ["young is not an integer","Nobody by name of Lise","Nobody by name of Mike"]
-- >>> Rank2.distribute [alice, bob, charlie]
-- Person{
-- name=Compose [Identity "Alice",Identity "Bob",Identity "Charlie"],
-- age=Compose [Identity 44,Identity 45,Identity 19],
-- mother=Compose [Identity Nothing,Identity Nothing,Identity (Just Person{
-- name=(Identity "Alice"),
-- age=(Identity 44),
-- mother=(Identity Nothing),
-- father=(Identity Nothing)})],
-- father=Compose [Identity Nothing,Identity Nothing,Identity (Just Person{
-- name=(Identity "Bob"),
-- age=(Identity 45),
-- mother=(Identity Nothing),
-- father=(Identity Nothing)})]}
```

Grammars are another use case that is almost, but not quite, completely unlike database records. See grammatical-parsers about that.

## Changes

## Version 1.3

- Added
`newtype Flip`

to exports - PR by Jeremy List - Generating INLINE pragmas from Rank2.TH
- Generating the proper constraints on derived instances where needed

## Version 1.2.1

- Added unsafeDeriveApply

## Version 1.2

- Added the class instances for Data.Functor.Const
- Fixed and optimized the Foldable/Traversable instance code generated for bare fields in Rank2.TH

## Version 1.1

- Replaced own
`Product`

data type by the one from`Data.Functor.Product`

- Added instances of
`Data.Functor.Sum`

- Removed the TH generation of partial Apply and Distributive instances
- Covered more constructor cases in TH code
- Added use-template-haskell flag, true by default - PR by Dridus

## Version 1.0.2

- Fixed the bounds and
`Semigroup`

to compile with GHC 8.4.1 - Added the ~> type synonym
- Fixed
`deriveFunctor`

for record fields with concrete types - PR by Tom Smalley

## Version 1.0.1

- Fixed the doctests

## Version 1.0

- Swapped
`distributeWith`

with`cotraverse`

- Documentation improvements

## Version 0.2.1.1

- Corrected the README

## Version 0.2.1

- Incremented the dependency bounds for GHC 8.2.1

## Version 0.2

- Introduced
`DistributiveTraversable`

as a generalization of`Distributive`

- Export “cotraverse” and “cotraverseTraversable”
- Added
`liftA3`

,`liftA4`

,`liftA5`

- Added more convienence functions
- Fixed grammatical errors and overlong lines

## Version 0.1.1

- Generalized the classes with
`{-# LANGUAGE PolyKinds" #-}`

## Version 0.1

- Initial release

*(full list with versions)*: