{"id":17251109,"url":"https://github.com/fumieval/tangle","last_synced_at":"2025-04-14T05:21:01.023Z","repository":{"id":66326292,"uuid":"239242675","full_name":"fumieval/tangle","owner":"fumieval","description":"make analogue for higher kinded data","archived":false,"fork":false,"pushed_at":"2021-11-26T06:13:34.000Z","size":17,"stargazers_count":8,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-27T19:09:07.765Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://hackage.haskell.org/package/tangle","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/fumieval.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-09T03:51:17.000Z","updated_at":"2025-01-24T15:40:00.000Z","dependencies_parsed_at":null,"dependency_job_id":"cb2bd735-7b2d-467d-b710-2d5485c27798","html_url":"https://github.com/fumieval/tangle","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/fumieval%2Ftangle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fumieval%2Ftangle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fumieval%2Ftangle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fumieval%2Ftangle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fumieval","download_url":"https://codeload.github.com/fumieval/tangle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248824993,"owners_count":21167412,"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":[],"created_at":"2024-10-15T06:50:21.893Z","updated_at":"2025-04-14T05:21:01.000Z","avatar_url":"https://github.com/fumieval.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"tangle\n----\n\nThis package implements an abstraction of record construction where each field may depend on other fields.\nIt is a reimplementation of [extensible's Tangle](https://hackage.haskell.org/package/extensible-0.8.3/docs/Data-Extensible-Tangle.html) refined for more general HKDs.\n\n```haskell\nevalTangleT :: Tangle t m a -- ^ computation\n  -\u003e t (Tangle t m) -- ^ collection of tangles\n  -\u003e m a\n\nhitch\n :: Monad m\n  =\u003e (forall h. Lens' (t h) (h a)) -- ^ the lens of the field\n  -\u003e Tangle t m a\n```\n\nA computation tangle is a higher-kinded record of `Tangle t`. Each field may fetch other fields by using `hitch`; the result is memoised so the computation is performed at most once for each field. Recursive `hitch` is not supported (becomes an infinite loop).\n\n`examples/weight.hs` demonstrates a simple program that calculates a body mass index.\n\nFirst, we define a highed-kinded record of parameters.\n```haskell\ndata Info h = Info\n  { _height :: h Double\n  , _mass :: h Double\n  , _bmi :: h Double\n  , _isHealthy :: h Bool\n  } deriving Generic\ninstance FunctorB Info\ninstance ApplicativeB Info\nmakeLenses ''Info\n```\n\nThen describe how to compute each field as a record of `TangleT`s.\nNote the `_bmi` field fetching `height` and `mass`.\n\n```haskell\nbuildInfo :: Info (TangleT Info IO)\nbuildInfo = Info\n  { _height = liftIO $ prompt \"Height(m): \"\n  , _mass = liftIO $ prompt \"Mass(kg): \"\n  , _bmi = do\n    h \u003c- hitch height\n    m \u003c- hitch mass\n    return $! m / (h * h)\n  , _isHealthy = do\n    x \u003c- hitch bmi\n    pure $ x \u003e= 18 \u0026\u0026 x \u003c 22\n  }\n```\n\nFinally, we can run a computation `go` with `evalTangleT`.\nBehold, it asks for height and mass only once, even though the `bmi` field is used twice (via `isHealthy`).\n\n```haskell\nmain :: IO ()\nmain = evalTangleT go buildInfo \u003e\u003e= print where\n  go = (,) \u003c$\u003e hitch bmi \u003c*\u003e hitch isHealthy\n```\n\nWhat are tangles?\n----\n\nTangle is a __memoisation__ monad for __heterogenous collections__ such as records.\nIf the collection were just a homogenous container, it is equivalent to\n\n```haskell\ntype Key = String\ntype Container = Map.Map Key\n\nnewtype Tangle b a = Tangle { unTangle :: ReaderT (Container (Tangle b b)) (State (Container b)) a }\n  deriving (Functor, Applicative, Monad)\n```\n\nwhere `Container (Tangle b)` is a collection of computations which would yield the final results,\nand `Container b` is the cache of intermediate results.\n\nWith these in mind, the following function can be defined:\n\n```haskell\nhitch :: Key -\u003e Tangle b b\nhitch key = Tangle $ ReaderT $ \\env -\u003e state $ \\cache -\u003e case Map.lookup key cache of\n  -- If the requested key exists in the cache, just return the value\n  Just a -\u003e (a, cache)\n  Nothing -\u003e case Map.lookup key env of\n    Nothing -\u003e error \"Not found\"\n    Just m -\u003e\n      -- Compute the result\n      let (result, cache') = unTangle m `runReaderT` env `runState` cache\n      -- Insert the result to the cache\n      in (result, Map.insert key result cache')\n```\n\nEach computation in `Container (Tangle b b)` may `hitch` a value from other keys.\nSince `hitch` caches its result, subsequent calls just return the same value;\nthat's how `Tangle` makes dependent computation composable.\n\nIn `Control.Monad.Tangle`, the container type is generalised to any higher-kinded datatypes.\nHKD allows the container type to be in two forms: `t (Tangle t)` (collection of computations) and `t Maybe` (cache of results).\nNot just it's much more flexible, the `ApplicativeB` constaint also ensures that the collection and the cache have the same shape.\n\nApplication\n----\n\nTangles can split dependent computations into smaller pieces of code without explicity passing values by function arguments.\nIt implies that it's trivial to add/remove dependencies between computation, because they are automatically handled in the memoisation mechanism.\nOne useful property is that the cache can be reused as the second argument of `runTangleFT`.\nIf some fields are expensive to calculate and the rest is expensive to serialise,\nthe server application could compute the former, distribute the cache, and then let the clients fill the rest.\n\nSee also\n----\n\n* [Tangle: the dynamic programming monad](https://discourse.haskell.org/t/tangle-the-dynamic-programming-monad/2406)\n* [拡張可能タングルでDo記法レスプログラミング♪ (Haskell)](https://matsubara0507.github.io/posts/2018-02-22-fun-of-extensible-3.html)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffumieval%2Ftangle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffumieval%2Ftangle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffumieval%2Ftangle/lists"}