Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/kayhide/wakame
Haskell library of row polymorphic record manipulator
https://github.com/kayhide/wakame
hacktoberfest haskell
Last synced: 2 months ago
JSON representation
Haskell library of row polymorphic record manipulator
- Host: GitHub
- URL: https://github.com/kayhide/wakame
- Owner: kayhide
- License: bsd-3-clause
- Created: 2020-04-21T11:01:48.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2020-10-19T05:08:43.000Z (over 4 years ago)
- Last Synced: 2024-04-25T22:30:31.654Z (9 months ago)
- Topics: hacktoberfest, haskell
- Language: Haskell
- Homepage: http://github.com/kayhide/wakame
- Size: 78.1 KB
- Stars: 19
- Watchers: 5
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: ChangeLog.md
- License: LICENSE
Awesome Lists containing this project
README
# Wakame
[![GitHub CI](https://github.com/kayhide/wakame/workflows/CI/badge.svg)](https://github.com/kayhide/wakame/actions)
[![Hackage](https://img.shields.io/hackage/v/wakame.svg)](https://hackage.haskell.org/package/wakame)
[![Stackage LTS](http://stackage.org/package/wakame/badge/lts)](http://stackage.org/lts/package/wakame)
[![Stackage Nightly](http://stackage.org/package/wakame/badge/nightly)](http://stackage.org/nightly/package/wakame)
![BSD3 license](https://img.shields.io/badge/license-BSD3-blue.svg)`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 (toRow updating) (toRow user)
```Here is a working example of using this function:
```haskell
> user
User {id = ID 42, email = "[email protected]", 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 = "[email protected]", username = "Spider Man"}
> updateUser updating user
User {id = ID 42, email = "[email protected]", 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 (toRow $ keyed @"updated_at" time) (toRow user)
````toRow $ 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 (toRow $ updating) $ union (toRow $ keyed @"updated_at" time) (toRow user)
```This function works as follows:
```haskell
> updateAndTouchUser updating time user
User {id = ID 42, email = "[email protected]", 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 ytype 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!