wakame
Functions to manipulate records
https://github.com/kayhide/wakame#readme
| LTS Haskell 24.16: | 0.1.0.0 |
| Stackage Nightly 2025-10-24: | 0.1.0.0 |
| Latest on Hackage: | 0.1.0.0 |
wakame-0.1.0.0@sha256:fb959329fd07df9815c8586c754f14e73e1a337de4d342017abd044f30f82b05,5242Module documentation for 0.1.0.0
Wakame
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:
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:
data UpdatingUser =
UpdatingUser
{ email :: Text
, username :: Text
}
deriving Generic
Then, write a function for doing the update:
updateUser :: UpdatingUser -> User -> User
updateUser updating user = fromRec $ nub $ union (toRec updating) (toRec user)
Here is a working example of using this function:
> 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:
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:
{ updated_at :: UTCTime }
And updating the user and the updated_at field can be done easily within the
same function:
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:
> 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 unions will be faster than using nub
after every individual union.
Wrapping up, we have done the following:
- Converting a record into its corresponding
Rowrepresentation with thetoRowfunction - Adding, removing or replacing the fields over the
Rowwithunionandnub - Converting back to a record with
fromRow
Row-polymorphic functions
The following create and update functions are generalized in terms of row-polymorphism.
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
IsRowis a constraint which defines theOftype family and a pair oftoRow/fromRowfunctions.wakamedefines an instance ofIsRowfor all Haskell records with aGenericinstance.
Lacksconstrains a row to not have a field with the given label.Mergeis a combination ofUnionandNub, 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.
There are other examples available at Wakame.Examples.Functons.
If you’re interested in row polymorphism, the Wikipedia page may help: Row polymorphism.
A direct translation of the functions described in the Wikipedia page is also available at Wakame.Examples.RowPolymorphism.
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 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.
Why not record-sop ?
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 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!