{"id":21478516,"url":"https://github.com/kayhide/wakame","last_synced_at":"2025-07-15T11:31:11.600Z","repository":{"id":56882400,"uuid":"257567282","full_name":"kayhide/wakame","owner":"kayhide","description":"Haskell library of row polymorphic record manipulator","archived":false,"fork":false,"pushed_at":"2020-10-19T05:08:43.000Z","size":80,"stargazers_count":19,"open_issues_count":0,"forks_count":1,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-04-25T22:30:31.654Z","etag":null,"topics":["hacktoberfest","haskell"],"latest_commit_sha":null,"homepage":"http://github.com/kayhide/wakame","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kayhide.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-04-21T11:01:48.000Z","updated_at":"2024-02-19T17:16:01.000Z","dependencies_parsed_at":"2022-08-21T00:20:14.103Z","dependency_job_id":null,"html_url":"https://github.com/kayhide/wakame","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kayhide%2Fwakame","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kayhide%2Fwakame/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kayhide%2Fwakame/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kayhide%2Fwakame/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kayhide","download_url":"https://codeload.github.com/kayhide/wakame/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226033862,"owners_count":17563249,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","haskell"],"created_at":"2024-11-23T11:18:40.698Z","updated_at":"2024-11-23T11:18:41.293Z","avatar_url":"https://github.com/kayhide.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Wakame\n\n[![GitHub CI](https://github.com/kayhide/wakame/workflows/CI/badge.svg)](https://github.com/kayhide/wakame/actions)\n[![Hackage](https://img.shields.io/hackage/v/wakame.svg)](https://hackage.haskell.org/package/wakame)\n[![Stackage LTS](http://stackage.org/package/wakame/badge/lts)](http://stackage.org/lts/package/wakame)\n[![Stackage Nightly](http://stackage.org/package/wakame/badge/nightly)](http://stackage.org/nightly/package/wakame)\n![BSD3 license](https://img.shields.io/badge/license-BSD3-blue.svg)\n\n`wakame` is a Haskell library to manipulate record fields in a row-polymorphic way.\n\n## Overview\n\nHere is a quick overview of what `wakame` provides.\n\nImagine a data type of:\n\n```haskell\ndata User =\n  User\n  { id :: ID User\n  , email :: Text\n  , username :: Text\n  , created_at :: UTCTime\n  , updated_at :: UTCTime\n  }\n  deriving Generic\n```\n\nTo update a subset of the `User` record's fields, first define a data type containing the fields you want to update:\n\n```haskell\ndata UpdatingUser =\n  UpdatingUser\n  { email :: Text\n  , username :: Text\n  }\n  deriving Generic\n```\n\nThen, write a function for doing the update:\n\n\n```haskell\nupdateUser :: UpdatingUser -\u003e User -\u003e User\nupdateUser updating user = fromRec $ nub $ union (toRow updating) (toRow user)\n```\n\nHere is a working example of using this function:\n\n```haskell\n\u003e user\nUser {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}\n\u003e updating\nUpdatingUser {email = \"spider@man.com\", username = \"Spider Man\"}\n\u003e updateUser updating user\nUser {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}\n```\n\nUpdating the `updated_at` field in `User` can be done in the same manner.  But\nthis time, let's do it without defining a separate record type:\n\n```haskell\ntouchUser :: UTCTime -\u003e User -\u003e User\ntouchUser time user = fromRec $ nub $ union (toRow $ keyed @\"updated_at\" time) (toRow user)\n```\n\n`toRow $ keyed @\"update_at\" time` creates a `Row` object which has only one field:\n\n```haskell\n{ updated_at :: UTCTime }\n```\n\nAnd updating the user and the `updated_at` field can be done easily within the\nsame function:\n\n```haskell\nupdateAndTouchUser :: UpdatingUser -\u003e UTCTime -\u003e User -\u003e User\nupdateAndTouchUser updating time user =\n  fromRec $ nub $ union (toRow $ updating) $ union (toRow $ keyed @\"updated_at\" time) (toRow user)\n```\n\nThis function works as follows:\n\n```haskell\n\u003e updateAndTouchUser updating time user\nUser {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}\n```\n\nNote that using `nub` once after a chain of `union`s will be faster than using `nub`\nafter every individual `union`.\n\nWrapping up, we have done the following:\n\n- Converting a record into its corresponding `Row` representation with the `toRow` function\n- Adding, removing or replacing the fields over the `Row` with `union` and `nub`\n- Converting back to a record with `fromRow`\n\n## Row-polymorphic functions\n\nThe following `create` and `update` functions are generalized in terms of row-polymorphism.\n\n```haskell\ndata ModelBase a =\n  ModelBase\n  { id         :: ID a\n  , created_at :: UTCTime\n  , updated_at :: UTCTime\n  }\n  deriving (Eq, Show, Generic)\n\n\ncreate ::\n  forall a b.\n  ( IsRow a\n  , IsRow b\n  , Lacks \"id\" (Of a)\n  , Merge (Of a) (Of (ModelBase b)) (Of b)\n  ) =\u003e a -\u003e IO b\ncreate x = do\n  now \u003c- getCurrentTime\n  id' \u003c- pure $ ID @b 42 -- shall be `getNextID` or something in practice.\n  let y =\n        fromRow\n        $ merge (toRow x)\n        $ toRow $ ModelBase @b id' now now\n  pure y\n\n\ntype OfUpdatedAt = '[ '(\"updated_at\", UTCTime) ]\n\nupdate ::\n  ( IsRow a\n  , IsRow b\n  , Union (Of a) (Of b) ab\n  , Merge OfUpdatedAt ab (Of b)\n  ) =\u003e a -\u003e b -\u003e IO b\nupdate updating x = do\n  now \u003c- getCurrentTime\n  let y =\n        fromRow\n        $ merge (toRow $ keyed @\"updated_at\" now)\n        $ union (toRow updating)\n        $ toRow x\n  pure y\n```\n\n- `IsRow` is a constraint which defines the `Of` type family and a pair of\n  `toRow` / `fromRow` functions.\n  - `wakame` defines an instance of `IsRow` for all Haskell records with a `Generic` instance.\n- `Lacks` constrains a row to not have a field with the given label.\n- `Merge` is a combination of `Union` and `Nub`, which do appending and removing respectively.\n\nWith these constraints and functions, you can easily write row polymorphic\nfunctions in your application.\n\nThese examples are found at \n[Wakame.Examples.Usage](https://github.com/kayhide/wakame/blob/master/test/examples/Wakame/Examples/Usage.hs).\n\nThere are other examples available at \n[Wakame.Examples.Functons](https://github.com/kayhide/wakame/blob/master/test/examples/Wakame/Examples/Functions.hs).\n\nIf you're interested in row polymorphism, the Wikipedia page may help: \n[Row polymorphism](https://en.wikipedia.org/wiki/Row_polymorphism).\n\nA direct translation of the functions described in the Wikipedia page is also available at \n[Wakame.Examples.RowPolymorphism](https://github.com/kayhide/wakame/blob/master/test/examples/Wakame/Examples/RowPolymorphism.hs).\n\n\n\n## Underlying data structure\n\n`wakame` uses `NP` (a.k.a. \"N-ary Product\") as the underlying representation of\n`Row`.  `NP` is a data type from the\n[sop-core](https://hackage.haskell.org/package/sop-core) library.\n\nSo if you need finer control of `Row`, or if you need an advanced or\napplication-specific operation, you have the option of using the `NP` data type\ndirectly, which will allow you to take advantage of the rich set of functions\nfrom the `sop-core` library.\n\nFor more details, see the paper [True Sums Of Products](https://www.andres-loeh.de/TrueSumsOfProducts/).\n\n\n### Why not `record-sop` ?\n\n[records-sop](https://hackage.haskell.org/package/records-sop) is a library\nbuilt on top of `sop-core`.  It focuses on the representation of a record data\ntype and provides a set of functions for doing conversions.\n\nThe difference is that `records-sop` is relying on `generics-sop` which is more \ngeneral and also covers non-record data types. \n`wakame` is specialized for only record data types.\n\nAlthough the representation data types is virtually the same between\n`records-sop` and `wakame`, how to convert between data types is different.\n\nOne of the benefits of `wakame` is the ability to introduce special conversion \nrules such as `keyed @\"label\" value` to / from `Row`.\n\n`wakame` gives you the ability to make a single `keyed` value correspond to the\nrepresentation of a data type with one field, and any arbitrary tuple of\n`keyed` values to a data type with multiple fields.\nIn this way, you can use a tuple of `keyed` values in place of an anonymous record.\n\n## What is wakame?\n\n[Wakame](https://en.wikipedia.org/wiki/Wakame) is a type of edible seaweed, popular in Japan.\n\nThe most important property of wakame is that, it changes its color when boiled.\n\n## Contributions\n\nFeel free to open an issue or PR.\nThanks!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkayhide%2Fwakame","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkayhide%2Fwakame","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkayhide%2Fwakame/lists"}