{"id":16879427,"url":"https://github.com/purefunctor/purescript-uncurried-transformers","last_synced_at":"2026-02-22T23:33:35.399Z","repository":{"id":110141783,"uuid":"492413393","full_name":"purefunctor/purescript-uncurried-transformers","owner":"purefunctor","description":"Stack-safe monad transformers implemented using continuation-passing style and uncurried functions.","archived":false,"fork":false,"pushed_at":"2022-08-28T13:29:12.000Z","size":136,"stargazers_count":23,"open_issues_count":3,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-20T02:41:24.490Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"PureScript","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/purefunctor.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.d/README.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-05-15T07:07:28.000Z","updated_at":"2025-08-20T13:20:09.000Z","dependencies_parsed_at":"2023-09-22T16:09:19.988Z","dependency_job_id":null,"html_url":"https://github.com/purefunctor/purescript-uncurried-transformers","commit_stats":{"total_commits":40,"total_committers":1,"mean_commits":40.0,"dds":0.0,"last_synced_commit":"221fe4b5f404cf9b8149605f1d054946f4f0ab0d"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/purefunctor/purescript-uncurried-transformers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purefunctor%2Fpurescript-uncurried-transformers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purefunctor%2Fpurescript-uncurried-transformers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purefunctor%2Fpurescript-uncurried-transformers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purefunctor%2Fpurescript-uncurried-transformers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/purefunctor","download_url":"https://codeload.github.com/purefunctor/purescript-uncurried-transformers/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purefunctor%2Fpurescript-uncurried-transformers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29730743,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T20:09:16.275Z","status":"ssl_error","status_checked_at":"2026-02-22T20:09:13.750Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":"2024-10-13T15:54:18.652Z","updated_at":"2026-02-22T23:33:35.372Z","avatar_url":"https://github.com/purefunctor.png","language":"PureScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# purescript-uncurried-transformers\nStack-safe monad transformers implemented using continuation-passing style and uncurried functions.\n\n## Installation\n```\nspago install uncurried-transformers\n```\n\n## Vs. Transformers\n\n### At the Surface Level\n\nAt the surface-level, the only significant difference between this library and `transformers` is that runner functions like `runStateT`, `runReaderT` accept the monadic actions as their last arguments. Unlike in Haskell where the record accessor dictates that it should be first, we have no such restriction in PureScript, hence, the decision to use more convenient and composable syntax.\n\n### How and Why It Goes Fast\n\n`uncurried-transformers`, as the name and description implies, is implemented using uncurried functions and continuation-passing style. The core type, `RWSET`, is implemented as a newtype over `Fn6` with a bunch of seemingly magical parameters:\n\n```purescript\nnewtype RWSET r w s e m a = RWSET\n  ( forall c\n     . Fn6\n         -- Environment\n         r\n         -- State\n         s\n         -- Trampoline\n         ((Unit -\u003e c) -\u003e c)\n         -- Lift\n         (m (Unit -\u003e c) -\u003e c)\n         -- Error\n         (Fn3 s e w c)\n         -- Success\n         (Fn3 s a w c)\n         -- Continuation\n         c\n  )\n```\n\nIn essence, this is a JavaScript function with 6 (uncurried) arguments, the first two being the `Reader` environment and the `State` state, and the following four being special callbacks which help produce some opaque `c` type, which is\nalso used as the return type for our uncurried function. What `c` represents doesn't matter right now, but it's important to look out how these callbacks may be used. Let's take a look at some instances for `RWSET`:\n\nThe `Applicative` instance, for example, runs the success continuation `done` in order to produce a `c` to be returned; it's passed the current `state`, a result `a` for the monadic action, and a `mempty` result for the writer.\n\n```purescript\ninstance Monoid w =\u003e Applicative (RWSET r w s e m) where\n  pure a = RWSET\n    ( mkFn6 \\_ state _ _ _ done -\u003e\n        runFn3 done state a mempty\n    )\n```\n\nOn the other hand, the `MonadThrow` instance runs the failure continuation `error`, in order to produce a `c` to be returned; it's passed the current `state`, an error `e` for the monadic action, and a `mempty` result for the writer.\n\n```purescript\ninstance Monoid w =\u003e MonadThrow e (RWSET r w s e m) where\n  throwError e = RWSET\n    ( mkFn6 \\_ state _ _ error _ -\u003e\n        runFn3 error state e mempty\n    )\n```\n\nThe `Functor` instance demonstrates a basic use of the trampoline continuation `more`; what it essentially does is that it \"thunks\" a result `c` such that some stack space is freed before it's evaluated, effectively making it lazy. In order to `map` an `f` over `RWSET`, we have to run `k` with a custom `done` callback such that `f` is applied over the result `a`. As a rule of thumb, we use `more` liberally in order to preserve the stack-safety of our program.\n\n```purescript\ninstance Functor (RWSET r w s e m) where\n  map f (RWSET k) = RWSET\n    ( mkFn6 \\environment state0 more lift' error done -\u003e\n        more \\_ -\u003e runFn6 k environment state0 more lift' error\n          ( mkFn3 \\state1 a w -\u003e\n              more \\_ -\u003e runFn3 done state1 (f a) w\n          )\n    )\n```\n\nLast but not least, the `lift` continuation acts as a thunk over the base monad `m`, much like `more` acts as a thunk for non-monadic values. For `MonadTrans`, we effectively take the `m a` and turn it into `m (Unit -\u003e c)`, before being passed to `lift` for the final `c` value.\n\n```purescript\ninstance Monoid w =\u003e MonadTrans (RWSET r w s e) where\n  lift m = RWSET\n    ( mkFn6 \\_ state _ lift' _ done -\u003e\n        lift' (map (\\a _ -\u003e runFn3 done state a mempty) m)\n    )\n```\n\nHold on, if the `c` type is completely opaque, then how are we supposed to run the `RWSET` monad? The answer lies in the fact that we can reify `c` into \"any\" type that we want, but for this library in particular, we choose to reify the continuations in the form of an ADT. Notice how each variant of the following type corresponds to the continuations in `RWSET`, except each `c` is replaced with a concrete type. Similarly, we can just encode the failure and success continuations using an `Either`!\n\n```purescript\ndata RunRWSET r w s e m a\n  = More (Unit -\u003e RunRWSET r w s e m a)\n  | Lift (m (Unit -\u003e RunRWSET r w s e m a))\n  | Stop s (Either e a) w\n```\n\nUsing an ADT allows us to write a tail-recursive loop that \"interprets\" the `RWSET` monad, as shown in the code block below. Focusing first on the latter half, we can see that the uncurried function `k` is ran with the environment `r`, the state `s`, the `More` constructor as the trampoline, the `Lift` constructor as the lift, and `Stop` to encode `Either` an error `e` or a result `a`, along with the final state `s` and the writer result `w`.\n\n```purescript\nrunRWSET r s (RWSET k) =\n  let\n    go step = case step unit of\n      More n -\u003e\n        go n\n      Lift m -\u003e\n        Loop \u003c$\u003e m\n      Stop s' a w -\u003e\n        pure $ Done $ s' /\\ a /\\ w\n  in\n    tailRecM go \\_ -\u003e\n      runFn6 k r s More Lift\n        (mkFn3 \\s' e w -\u003e Stop s' (Left e) w)\n        (mkFn3 \\s' a w -\u003e Stop s' (Right a) w)\n```\n\nWe then analyze its result using the auxiliary function `go`. If the result of running `k` is `More`, we tail-recurse into `go` with the thunk `n`; if the result is `Lift`, we tail-recurse into `go` (with the help of `tailRecM`) such that the monadic action is ran before the thunk is evaluated; if the result is `Stop`, we just signal to `tailRecM` that we're done computing and we have our final result. This tail-recursive loop is what allows us to keep stack-safety at the cost of some heap allocations. \n\n### (Real-World) Benchmarks\n\nSee the benchmark code [here](./bench/Main.purs). For `transformers`, the `RWST` type with a `Trampoline` base is used, while for `uncurried-transformers`, the equivalent `RWSET` type is used with `Identity` as its base. In the following graphs, `naive-` means recursion is performed naively i.e. without the help of `tailRecM`; its presence corresponds to the `tailrecm` prefix instead.\n\n![Performance benchmarks for a state-based counter to a certain limit.](./bench/countToN.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpurefunctor%2Fpurescript-uncurried-transformers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpurefunctor%2Fpurescript-uncurried-transformers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpurefunctor%2Fpurescript-uncurried-transformers/lists"}