{"id":16348911,"url":"https://github.com/sheaf/acts","last_synced_at":"2025-08-26T02:16:33.685Z","repository":{"id":62435521,"uuid":"240231058","full_name":"sheaf/acts","owner":"sheaf","description":"Haskell library for semigroup actions and torsors","archived":false,"fork":false,"pushed_at":"2025-02-03T09:28:10.000Z","size":60,"stargazers_count":62,"open_issues_count":0,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-26T21:16:02.512Z","etag":null,"topics":["haskell-library","semigroup-actions","transformations"],"latest_commit_sha":null,"homepage":"","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":"2020-02-13T10:14:19.000Z","updated_at":"2025-04-29T19:49:38.000Z","dependencies_parsed_at":"2024-10-27T10:57:58.500Z","dependency_job_id":"3920f1ba-ff26-48ad-bcd0-064752e606a5","html_url":"https://github.com/sheaf/acts","commit_stats":{"total_commits":11,"total_committers":1,"mean_commits":11.0,"dds":0.0,"last_synced_commit":"395acb738ca7e994ecadedd06f191f9a759ecc78"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sheaf/acts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Facts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Facts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Facts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Facts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sheaf","download_url":"https://codeload.github.com/sheaf/acts/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Facts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272160244,"owners_count":24883780,"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","status":"online","status_checked_at":"2025-08-26T02:00:07.904Z","response_time":60,"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":["haskell-library","semigroup-actions","transformations"],"created_at":"2024-10-11T00:55:47.988Z","updated_at":"2025-08-26T02:16:33.632Z","avatar_url":"https://github.com/sheaf.png","language":"Haskell","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"img/coerce.svg\" alt=\"Defining a torsor by transporting a group operation\"\u003e\n\u003c/p\u003e\n\n\n# Acts \u003ca href=\"https://hackage.haskell.org/package/acts\" alt=\"Hackage\"\u003e\u003cimg src=\"https://img.shields.io/hackage/v/acts.svg\" /\u003e\u003c/a\u003e\n\n* [Introduction](#intro)\n* [Examples](#examples)\n  - [Points and vectors](#affinespace)\n  - [Time](#time)\n  - [Musical intervals](#intervals)\n* [Comparison with existing libraries](#comparison)\n\n\u003ca name=\"intro\"\u003e\u003c/a\u003e\n# Introduction\n\n**acts** is a Haskell library for semigroup actions and torsors.\n\nAn *act* denotes the ability to apply transformations in a compositional way:\n\n```haskell\nact g ( act h x ) = act ( g \u003c\u003e h ) x\n```\n\nThat is, the target of an act is not necessarily a semigroup, but can be transformed by a semigroup.\n\nNote that this is a *left* act (elements of the semigroup act on the left).\nThis library does not define right acts, but they can be emulated as left acts of the opposite semigroup by using the `Dual` newtype.\n\n\nWhen there's a unique transformation taking one element to another element, the act is said to be a *torsor*:\n\n```haskell\nact ( x --\u003e y ) x = y\n```\n\n\n\u003ca name=\"examples\"\u003e\u003c/a\u003e\n# Examples\n\n\u003ca name=\"affinespace\"\u003e\u003c/a\u003e\n## Points and vectors\n\nAddition of points in the plane is not meaningful, but points can be translated by spatial vectors, and given any two points there is a unique translation taking the first point to the second point.\n\n```haskell\ndata Point2D a = Point2D !a !a\n  deriving stock ( Show, Generic )\n  deriving ( Act ( Vector2D a ), Torsor ( Vector2D a ) )\n    via Vector2D a\nnewtype Vector2D a = Vector2D { tip :: Point2D a }\n  deriving stock Show\n  deriving ( Semigroup, Monoid, Group )\n    via GenericProduct ( Point2D ( Sum a ) )\n```\n\nHere we use `DerivingVia` and generics (with [generic-data](https://hackage.haskell.org/package/generic-data) and [groups-generic](https://hackage.haskell.org/package/groups-generic)) to obtain the relevant instances.\n\n```haskell\np1, p2, p3 :: Point2D Double\np1 = Point2D 0.5 2.5\np2 = Point2D 1.0 0.0\np3 = Point2D 1.0 4.0\n\nv1, v2 :: Vector2D Double\nv1 = Vector2D ( Point2D 0.0 1.0 )\nv2 = Vector2D ( Point2D 1.0 1.0 )\n```\n\nWe can't add points:\n\n```haskell\n\u003e p1 \u003c\u003e p2\n```\n```\n* No instance for (Semigroup (Point2D Double))\n    arising from a use of `\u003c\u003e`\n```\n\nbut we can add vectors:\n\n```haskell\n\u003e v1 \u003c\u003e v2\nVector2D {tip = Point2D 1.0 2.0}\n```\n\nas well as reverse them:\n\n```haskell\n\u003e invert v1\nVector2D {tip = Point2D 0.0 (-1.0)}\n```\n\nTwo points form a vector:\n\n```haskell\nv12, v13, v23 :: Vector2D Double\nv12 = p1 --\u003e p2\nv13 = p1 --\u003e p3\nv23 = p2 --\u003e p3\n```\n\n```haskell\n\u003e v12\nVector2D {tip = Point2D 0.5 (-2.5)}\n```\n\nWe can translate points by vectors:\n\n```haskell\n\u003e act v12 p1\nPoint2D 1.0 0.0\n```\n\n```haskell\n\u003e act ( invert v12 ) p2\nPoint2D 0.5 2.5\n```\n\nTranslations compose:\n\n```haskell\n\u003e v13\nVector2D {tip = Point2D 0.5 1.5}\n```\n\n```haskell\n\u003e v23 \u003c\u003e v12\nVector2D {tip = Point2D 0.5 1.5}\n```\n\nAs we are acting on the left, transformations compose from right to left, just like function composition:\n\n```haskell\nact ( b --\u003e c ) . act ( a --\u003e b ) = act ( a --\u003e c )\n```\n\nThis looks slightly more intuitive using `(\u003c--)`:\n\n```haskell\nact ( c \u003c-- b ) . act ( b \u003c-- a ) = act ( c \u003c-- a )\n```\n\nNote that in the case of translation these distinctions are irrelevant, as translations form a *commutative* group.\n\n\u003ca name=\"time\"\u003e\u003c/a\u003e\n## Time\n\nWe can similarly distinguish absolute time (such as time obtained by querying the operating system) and time differences.\n\n```haskell\nnewtype Seconds   = Seconds { getSeconds :: Double }\n  deriving stock Show\n  deriving ( Act TimeDelta, Torsor TimeDelta )\n    via TimeDelta\nnewtype TimeDelta = TimeDelta { timeDeltaInSeconds :: Seconds }\n  deriving stock Show\n  deriving ( Semigroup, Monoid, Group )\n    via Sum Double\n```\n\nThe distinction is useful to prevent mixing up absolute times with time differences, so that one doesn't accidentally add two absolute times together:\n\n```haskell\n\u003e Seconds 3 \u003c\u003e Seconds 4\n```\n```\n* No instance for (Semigroup Seconds) arising from a use of `\u003c\u003e`\n```\n\nThis might seem trivial in isolation, but in the middle of a complex application it can be very helpful to have the compiler enforce this distinction,\nso that one doesn't have to e.g. remember whether the time value one has elsewhere stored in an `IORef` is absolute or relative.\n\nHaving to manually unwrap the `TimeDelta` newtype at use-sites would prove to be quite inconvenient;\ninstead the interface this library provides comes in handy:\n\n```haskell\n\u003e act ( TimeDelta ( Seconds 3 ) ) ( Seconds 4 )\nSeconds {getSeconds = 7.0}\n```\n\n```haskell\n\u003e Seconds 3 --\u003e Seconds 4 :: TimeDelta\nTimeDelta {timeDeltaInSeconds = Seconds {getSeconds = 1.0}}\n```\n\nThis can be used for instance to tick down a timer after some time has elapsed:\n\n```haskell\ncountdownTimer :: Seconds -\u003e Seconds -\u003e TimeDelta -\u003e TimeDelta\ncountdownTimer startTime endTime timeRemaining =\n  act ( endTime --\u003e startTime ) timeRemaining\n```\n\n\u003ca name=\"intervals\"\u003e\u003c/a\u003e\n## Musical intervals\n\n[Acts.Examples.MusicalIntervals](https://hackage.haskell.org/package/acts/docs/Acts-Examples-MusicalIntervals.html) demonstrates the application of this library to musical intervals.\n\nIn summary:\n\n  * Musical note names are a torsor under the cyclic group of order 7\n\n  ```haskell\n  -- Cyclic group of order 7.\n  type C7 = Sum ( Finite 7 ) -- 'Finite' from the 'finite-typelits' package\n\n  data NoteName = C | D | E | F | G | A | B\n    deriving stock    ( Eq, Show, Generic )\n    deriving anyclass Finitary\n    deriving ( Act C7, Torsor C7 )\n      via Finitely NoteName\n  ```\n\n  In this case we use the [finitary](https://hackage.haskell.org/package/finitary) package to derive the action,\n  using the newtype `Finitely`.\n\n  * Musical notes are a torsor under musical intervals, meaning that:\n\n    - we can transpose notes by musical intervals\n\n    ```haskell\n    diminished7th :: [ Interval ]\n    diminished7th = [ Interval 1 Natural, Interval 3 Flat, Interval 5 Flat, Interval 7 DoubleFlat ]\n    \n    \u003e map ( `act` Note G Sharp 3 ) diminished7th\n    [ \"G#3\", \"B3\", \"D4\", \"F4\" ]\n    ```\n\n    - we can compute intervals between notes\n\n    ```haskell\n    \u003e Note F Natural 3 --\u003e Note B Natural 3 :: Interval\n    Interval 4 Sharp\n    \"augmented 4th up\"\n    \n    \u003e Note E Flat 4 --\u003e Note C Flat 6 :: Interval\n    Interval 13 Flat\n    \"minor 13th up\"\n    ```\n\n\u003ca name=\"comparison\"\u003e\u003c/a\u003e\n# Comparison with existing libraries\n\nThe main purpose of this library is to provide convenient functionality for *torsors*. Other packages such as\n[torsor](https://hackage.haskell.org/package/torsor), [vector-space](https://hackage.haskell.org/package/vector-space), [simple-affine-space](https://hackage.haskell.org/package/simple-affine-space) and [Linear.Affine](https://hackage.haskell.org/package/linear/docs/Linear-Affine.html) concentrate on affine spaces (as reflected in their syntax), and as a result don't naturally cover the use case of finite actions (such as the action of a cyclic group on musical notes given above).    \n\nThe other design choice of this library is to focus on using newtypes and let users choose instances with `DerivingVia`,\ntrying to re-use newtypes from the `base` library when possible.     \nThis works around some difficulties with overlapping instances (although it is hardly a robust or comprehensive solution).    \nTo compare with other libraries that define semigroup actions (but not torsors):\n\n  * [semigroup-actions](https://hackage.haskell.org/package/semigroups-actions) uses a similar approach,\n  but the use of a newtype for the action of a semigroup on itself precludes many useful usages of `DerivingVia`\n  (such as the [affine space](#affinespace) and [time](#time) examples given above).\n  * [monoid-extras](https://hackage.haskell.org/package/monoid-extras) also defines newtypes for instances,\n  but these aren't easily usable with `DerivingVia` as they focus on the first (instead of last) type parameter.\n\nThis library is also careful to distinguish left and right actions, with the `Dual` newtype:\n\n```haskell\ninstance ( Semigroup s, Act s a, Act t b ) =\u003e Act ( Dual s, t ) ( a -\u003e b ) where\n  act ( Dual s, t ) p = act t . p . act s\n```\n\nThis captures the fact that, if `a` has a left action by `s`, and `b` a left action by `t`,\nthen `a -\u003e b` has a *right* action by `s` and a left action by `t`.\n\n\n\u003cbr\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsheaf%2Facts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsheaf%2Facts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsheaf%2Facts/lists"}