{"id":23908495,"url":"https://github.com/sheaf/coerce-data","last_synced_at":"2026-06-12T22:33:13.529Z","repository":{"id":270956609,"uuid":"911663319","full_name":"sheaf/coerce-data","owner":"sheaf","description":"Coerce between data types with this GHC typechecker plugin","archived":false,"fork":false,"pushed_at":"2025-01-04T10:13:38.000Z","size":8,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-23T15:36:58.227Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sheaf.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-03T14:51:41.000Z","updated_at":"2025-01-04T10:13:41.000Z","dependencies_parsed_at":null,"dependency_job_id":"674d47b4-25ca-4f02-9b0f-72e8f3b61553","html_url":"https://github.com/sheaf/coerce-data","commit_stats":null,"previous_names":["sheaf/coerce-data"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sheaf/coerce-data","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Fcoerce-data","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Fcoerce-data/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Fcoerce-data/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Fcoerce-data/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sheaf","download_url":"https://codeload.github.com/sheaf/coerce-data/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Fcoerce-data/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34265491,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-12T02:00:06.859Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2025-01-05T04:38:11.149Z","updated_at":"2026-06-12T22:33:13.517Z","avatar_url":"https://github.com/sheaf.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# coerce-data\n\n🚧 WORK IN PROGRESS 🚧\n\n\u003c!--\n\n# coerce-data \u003ca href=\"https://hackage.haskell.org/package/coerce-data\" alt=\"Hackage\"\u003e\u003cimg src=\"https://img.shields.io/hackage/v/coerce-data.svg\" /\u003e\u003c/a\u003e\n\nWhat if you could extend Haskell's iconic `coerce` function to work on data\ntypes that share a structure — not just newtypes? That's exactly what\n**coerce-data** brings to the table: safe, zero-cost coercions between\ndata types with isomorphic data constructors, powered by a GHC type-checking\nplugin.\n\n# Example\n\nExample:\n\n```hs\ndata D1 = MkD1 Int !Bool\ndata D2 a = MkD2 a !Bool\n\noneTwo :: [D1] -\u003e [D2 Int]\noneTwo = coerce\n```\n\nWith **coerce-data**, this works seamlessly. No hacks, no `unsafeCoerce`, just\ntype-safe, runtime-free elegance.\n\n# Motivation\n\nHaskell's `coerce` function is a cornerstone of zero-cost abstractions,\nallowing safe and efficient transformations between types.\n\nHowever, there's a catch: it only works with newtypes. This means many seemingly\nobvious conversions are off-limits, like converting between structurally\nidentical types with different names.\n\nUntil now, the workaround has often been to reach for `unsafeCoerce`, but let's\nbe honest: nobody likes debugging segfaults caused by a stray change in the\nstrictness of a data constructor argument in some distant library.  \nWith **coerce-data**, you can leave those worries behind.\n\n# Usage\n\nTo use **coerce-data**:\n\n  - Add `coerce-data` to the `build-depends` stanza in your `.cabal` file.\n  - Enable the plugin in any module where you'd like to use it:\n\n```hs\n{-# OPTIONS_GHC -fplugin=CoerceData.Plugin #-}\n```\n\n# Use-cases\n\n## Length-indexed vectors\n\nI often find myself making use of length-indexed vectors, in particular\nwith the following definition:\n\n```hs\ntype Vec :: Nat -\u003e Type -\u003e Type\ndata Vec n a where\n  VNil  :: Vec 0\n  (:::) :: a -\u003e Vec n a -\u003e Vec (1 + n) a\n```\n\nNote that this data type has the **exact** same structure as the list type,\nbecause the length argument is erased at runtime. With **coerce-data**, one can\nimmediately derive useful instances for this type, e.g.:\n\n```hs\nderiving via List    instance Functor     ( Vec n )\nderiving via List    instance Foldable    ( Vec n )\nderiving via List    instance Traversable ( Vec n )\nderiving via ZipList instance Applicative ( Vec n )\n```\n\n## Safely poking into a library's internals\n\nSometimes, you need to work with a library's internals, but patching the library\nto expose what you need — whether through public constructors or a `.Internal`\nmodule — just isn't an option. Perhaps you're building your own library\nand you don't want to force your users to upgrade their dependencies.\n\nConsider this scenario: a `fancy-library` exposes a type but hides its\nconstructors:\n\n```hs\n-- in 'fancy-library'\ndata FancyType\n  = InternalCon1 !Int\n  | InternalCon2 Bool\n```\n\nWith **coerce-data**, we can do the following:\n\n```hs\n-- in 'my-library'\ndata MyFancyType\n  = MyInternalCon1 !Int\n  | MyInternalCon2 Bool\n\npeekAndPoke :: (FancyType -\u003e FancyType) -\u003e MyFancyType -\u003e MyFancyType\npeekAndPoke = coerce\n```\n\nIf `FancyType` changes, this code won't compile until you update it,\nensuring it's easily maintainable: it doesn't make any unchecked assumptions\nabout the original type, unlike using `unsafeCoerce`.\n\n--\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsheaf%2Fcoerce-data","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsheaf%2Fcoerce-data","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsheaf%2Fcoerce-data/lists"}