packages feed

wakame-0.1.0.0: README.md

# Wakame

[![GitHub CI](https://github.com/kayhide/wakame/workflows/CI/badge.svg)](https://github.com/kayhide/wakame/actions)

hackage? comming soon

`wakame` is a Haskell library to manipulate record fields in a row-polymorphic way.

## Overview

Here is a quick overview of what `wakame` provides.

Imagine a data type of:

```haskell
data User =
  User
  { id :: ID User
  , email :: Text
  , username :: Text
  , created_at :: UTCTime
  , updated_at :: UTCTime
  }
  deriving Generic
```

To update a subset of the `User` record's fields, first define a data type containing the fields you want to update:

```haskell
data UpdatingUser =
  UpdatingUser
  { email :: Text
  , username :: Text
  }
  deriving Generic
```

Then, write a function for doing the update:


```haskell
updateUser :: UpdatingUser -> User -> User
updateUser updating user = fromRec $ nub $ union (toRec updating) (toRec user)
```

Here is a working example of using this function:

```haskell
> user
User {id = ID 42, email = "peter@amazing.com", username = "Peter Parker", created_at = 2020-06-16 11:22:11.991147596 UTC, updated_at = 2020-06-16 11:22:11.991147596 UTC}
> updating
UpdatingUser {email = "spider@man.com", username = "Spider Man"}
> updateUser updating user
User {id = ID 42, email = "spider@man.com", username = "Spider Man", created_at = 2020-06-16 11:22:11.991147596 UTC, updated_at = 2020-06-16 11:22:11.991147596 UTC}
```

Updating the `updated_at` field in `User` can be done in the same manner.  But
this time, let's do it without defining a separate record type:

```haskell
touchUser :: UTCTime -> User -> User
touchUser time user = fromRec $ nub $ union (toRec $ keyed @"updated_at" time) (toRec user)
```

`toRec $ keyed @"update_at" time` creates a `Row` object which has only one field:

```haskell
{ updated_at :: UTCTime }
```

And updating the user and the `updated_at` field can be done easily within the
same function:

```haskell
updateAndTouchUser :: UpdatingUser -> UTCTime -> User -> User
updateAndTouchUser updating time user =
  fromRec $ nub $ union (toRec $ updating) $ union (toRec $ keyed @"updated_at" time) (toRec user)
```

This function works as follows:

```haskell
> updateAndTouchUser updating time user
User {id = ID 42, email = "spider@man.com", username = "Spider Man", created_at = 2020-06-16 11:22:11.991147596 UTC, updated_at = 2020-06-16 11:31:35.170029827 UTC}
```

Note that using `nub` once after a chain of `union`s will be faster than using `nub`
after every individual `union`.

Wrapping up, we have done the following:

- Converting a record into its corresponding `Row` representation with the `toRow` function
- Adding, removing or replacing the fields over the `Row` with `union` and `nub`
- Converting back to a record with `fromRow`

## Row-polymorphic functions

The following `create` and `update` functions are generalized in terms of row-polymorphism.

```haskell
data ModelBase a =
  ModelBase
  { id         :: ID a
  , created_at :: UTCTime
  , updated_at :: UTCTime
  }
  deriving (Eq, Show, Generic)


create ::
  forall a b.
  ( IsRow a
  , IsRow b
  , Lacks "id" (Of a)
  , Merge (Of a) (Of (ModelBase b)) (Of b)
  ) => a -> IO b
create x = do
  now <- getCurrentTime
  id' <- pure $ ID @b 42 -- shall be `getNextID` or something in practice.
  let y =
        fromRow
        $ merge (toRow x)
        $ toRow $ ModelBase @b id' now now
  pure y


type OfUpdatedAt = '[ '("updated_at", UTCTime) ]

update ::
  ( IsRow a
  , IsRow b
  , Union (Of a) (Of b) ab
  , Merge OfUpdatedAt ab (Of b)
  ) => a -> b -> IO b
update updating x = do
  now <- getCurrentTime
  let y =
        fromRow
        $ merge (toRow $ keyed @"updated_at" now)
        $ union (toRow updating)
        $ toRow x
  pure y
```

- `IsRow` is a constraint which defines the `Of` type family and a pair of
  `toRow` / `fromRow` functions.
  - `wakame` defines an instance of `IsRow` for all Haskell records with a `Generic` instance.
- `Lacks` constrains a row to not have a field with the given label.
- `Merge` is a combination of `Union` and `Nub`, which do appending and removing respectively.

With these constraints and functions, you can easily write row polymorphic
functions in your application.

These examples are found at 
[Wakame.Examples.Usage](https://github.com/kayhide/wakame/blob/master/test/examples/Wakame/Examples/Usage.hs).

There are other examples available at 
[Wakame.Examples.Functons](https://github.com/kayhide/wakame/blob/master/test/examples/Wakame/Examples/Functions.hs).

If you're interested in row polymorphism, the Wikipedia page may help: 
[Row polymorphism](https://en.wikipedia.org/wiki/Row_polymorphism).

A direct translation of the functions described in the Wikipedia page is also available at 
[Wakame.Examples.RowPolymorphism](https://github.com/kayhide/wakame/blob/master/test/examples/Wakame/Examples/RowPolymorphism.hs).



## Underlying data structure

`wakame` uses `NP` (a.k.a. "N-ary Product") as the underlying representation of
`Row`.  `NP` is a data type from the
[sop-core](https://hackage.haskell.org/package/sop-core) library.

So if you need finer control of `Row`, or if you need an advanced or
application-specific operation, you have the option of using the `NP` data type
directly, which will allow you to take advantage of the rich set of functions
from the `sop-core` library.

For more details, see the paper [True Sums Of Products](https://www.andres-loeh.de/TrueSumsOfProducts/).


### Why not `record-sop` ?

[records-sop](https://hackage.haskell.org/package/records-sop) is a library
built on top of `sop-core`.  It focuses on the representation of a record data
type and provides a set of functions for doing conversions.

The difference is that `records-sop` is relying on `generics-sop` which is more 
general and also covers non-record data types. 
`wakame` is specialized for only record data types.

Although the representation data types is virtually the same between
`records-sop` and `wakame`, how to convert between data types is different.

One of the benefits of `wakame` is the ability to introduce special conversion 
rules such as `keyed @"label" value` to / from `Row`.

`wakame` gives you the ability to make a single `keyed` value correspond to the
representation of a data type with one field, and any arbitrary tuple of
`keyed` values to a data type with multiple fields.
In this way, you can use a tuple of `keyed` values in place of an anonymous record.

## What is wakame?

[Wakame](https://en.wikipedia.org/wiki/Wakame) is a type of edible seaweed, popular in Japan.

The most important property of wakame is that, it changes its color when boiled.

## Contributions

Feel free to open an issue or PR.
Thanks!