{"id":15366602,"url":"https://github.com/turbomack/glue","last_synced_at":"2025-10-07T12:51:51.895Z","repository":{"id":62419627,"uuid":"88343959","full_name":"turboMaCk/glue","owner":"turboMaCk","description":"Composing TEA modules with isolated state","archived":false,"fork":false,"pushed_at":"2020-03-02T11:52:26.000Z","size":308,"stargazers_count":29,"open_issues_count":0,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-28T20:11:57.203Z","etag":null,"topics":["elm","elm-architecture","tea"],"latest_commit_sha":null,"homepage":"http://package.elm-lang.org/packages/turboMaCk/glue/latest","language":"Elm","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/turboMaCk.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2017-04-15T11:55:15.000Z","updated_at":"2023-07-25T14:08:26.000Z","dependencies_parsed_at":"2022-11-01T17:02:46.377Z","dependency_job_id":null,"html_url":"https://github.com/turboMaCk/glue","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/turboMaCk%2Fglue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/turboMaCk%2Fglue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/turboMaCk%2Fglue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/turboMaCk%2Fglue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/turboMaCk","download_url":"https://codeload.github.com/turboMaCk/glue/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249072008,"owners_count":21208103,"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":["elm","elm-architecture","tea"],"created_at":"2024-10-01T13:19:16.807Z","updated_at":"2025-10-07T12:51:46.822Z","avatar_url":"https://github.com/turboMaCk.png","language":"Elm","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Glue\n\n[![Build Status](https://travis-ci.org/turboMaCk/glue.svg?branch=master)](https://travis-ci.org/turboMaCk/glue)\n\nThis package helps you reduce boilerplate when composing TEA-based (The Elm Architecture) applications using\n`Cmd.map`, `Sub.map` and `Html.map`.\n`Glue` is just a thin abstraction over these functions so it's easy to plug it in and out.\n\n**This package is highly experimental and might change a lot over time.**\n\nFeedback and contributions to both code and documentation are very welcome.\n\n[See demo](https://turbomack.github.io/glue/)\n\n## Important Note!\n\nThis package is not necessarily designed for either code splitting or reuse but rather for **state separation**.\nState separation might or might not be important for reusing certain parts of application.\nNot everything is necessarily stateful. For instance many UI parts can be expressed just by using `view` function\nto which you pass `msg` constructors (`view : msg -\u003e Model -\u003e Html msg` for instance) and let consumer manage its state.\nOn the other hand some things like larger parts of applications or parts containing a lot of self-maintainable stateful logic\ncan benefit from state isolation since it reduces state handling imposed on consumer of that module.\nGenerally it's a good rule of thumb to always choose simpler approach (And using stateless abstraction is usually simpler) -\n*if you aren't sure if you can benefit from extra isolation don't use it.* Always try to define as much logic as you can\nusing just simple functions and data. Then you can think about possible state separation in places where too much of it is exposed.\n**First rule is to avoid breaking of [single source of truth principle](https://en.wikipedia.org/wiki/Single_source_of_truth)**.\nIf you find yourself synchronizing some state from one place to another than that state shouldn't be probably isolated in first place.\n\n\n## tl;dr\n\nThis package is a result of my experience with building larger single page application in Elm where some modules live in isolation from others.\nThe goals and features of this package are:\n\n- Reduce boilerplate in `update` and `init` functions.\n- Reduce code flow indirection in gluing between parent and child module.\n- Define gluing logic in consumer module.\n- Enforce common interface in `init`, `update`, `subscribe` and `view`.\n- Make updates of nested models composable\n- *You should read the whole README anyway.*\n\n## Install\n\nAs you would expect...\n\n```\n$ elm install turboMaCk/glue\n```\n\n## Examples\n\nThe best place to start is probably to have a look at [examples](https://github.com/turboMaCk/glue/tree/master/examples).\n\nIn particular, you can find examples of:\n\n- [Composing Isolated Elm Apps together using Glue](https://github.com/turboMaCk/glue/tree/master/examples/Counter)\n- [Composing Modules with Subscriptions](https://github.com/turboMaCk/glue/tree/master/examples/Subscriptions)\n- [Action Bubbling (Sending Actions from Child to Parent)](https://github.com/turboMaCk/glue/tree/master/examples/Bubbling)\n\n## Why?\n\nTEA is an awesome way to write UI apps in Elm. However, not every application\nshould be defined just in terms of single `Model` and `Msg`.\nBasic separation of [`Browser.element`](https://package.elm-lang.org/packages/elm/browser/latest/Browser#element) is really nice\nbut in some cases these functions as well as `Model` and `Msg` types tend to\ngrow pretty quickly in an unmanageable way so you need to start breaking things\ndown.\n\nThere are [many ways](https://www.reddit.com/r/elm/comments/5jd2xn/how_to_structure_elm_with_multiple_models/dbkpgbd/)\nyou can go about it. In particular the rest of this document will focus just on\n[separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns).\nThis technique is useful for isolating parts that really don't need to know too\nmuch about each other.\nIt reduces scope of things a particular module can operate with so the number of\nthings the programmer has to reason about while adding or changing behaviour is\nlower. In TEA this is especially touching the `Msg` and `Model` types and `update`\nfunctions for managing the state.\n\n**It's important to understand that `init`, `update`, `view` and `subscriptions`\nare all isolated functions connected via `Browser.element`.\nIn pure functional programming we're \"never\" really managing state ourselves but\nare rather composing functions that takes state as data and produce new version\nof it (`update` function in TEA).**\n\nNow let's have a look at how we can use `Cmd.map`, `Sub.map` and `Html.map` for\nconcern separation in Elm application. We will nest `init`, `update`,\n`subscriptions` and `view` one into another and `map` them from child's to\nparent's types. Parent module is then using these units to manage just a subset\nof its overall state (`Model`). Here is how `Model` and `Msg` types of a parent\napplication might look like:\n\n```elm\nimport SubModule\n\ntype alias Model =\n    { ...\n    , subModuleModel : SubModule.Model\n    , ...\n    }\n\ntype Msg\n    = ...\n    | SubModuleMsg SubModule.Msg\n    | ...\n```\n\nBasically, the parent module only holds the `Model` of a child module\n(`SubModule`) as a single value, and wraps its `Msg` inside one of its own `Msg`\nconstructors (`SubModuleMsg`). Of course, `init`, `update` and `subscriptions`\nalso have to know how to work with this part of `Model`, and there you need\n`Cmd.map`, `Sub.map` and `Html.map`. For instance, this is how simple delegation\nof `Msg` in `update` might look:\n\n```elm\nupdate : Msg -\u003e Model -\u003e (Model, Cmd Msg)\nupdate msg model =\n    case msg of\n        ...\n        SubModuleMsg subMsg -\u003e\n            let\n                ( subModel, subCmd ) =\n                    SubModule.update subMsg model.subModuleModel\n            in\n                ( { model | subModuleModel = subModel }, Cmd.map SubModuleMsg subCmd )\n        ...\n```\n\nAs you can see, this is quite neat even though it requires some boiler-plate\ncode to deconstruct the pair and construct new one. One can as well utilize\n`Tuple.mapFirst` and `Tuple.mapSecond` function (bifunctor interface):\n\n```elm\nupdate : Msg -\u003e Model -\u003e (Model, Cmd Msg)\nupdate msg model =\n    case msg of\n        ...\n        SubModuleMsg subMsg -\u003e\n            SubModule.update subMsg model.subModuleModel\n                |\u003e Tuple.mapFirst (\\subModel -\u003e { model | subModel = subModel })\n                |\u003e Tuple.mapSecond (Cmd.map SubModuleMsg)\n        ...\n```\nLet's take a look at `view` and [`Html.map`](https://package.elm-lang.org/packages/elm/html/latest/Html#map) now:\n\n```elm\nview : Model -\u003e Html Msg\nview model =\n   Html.div []\n       [ ...\n       , Html.map SubModuleMsg \u003c| SubModule.view model.subModuleModel\n       , ...\n       ]\n```\n\nYou can use `Cmd.map` inside `init` as well as `Sub.map` which is fairly similar\nto `Html.map` in `subscriptions` to finish wiring of a child module (`SubModule`).\n\nAnd this is as far as pure TEA goes. This may possibly be good fit for your needs,\nand that's OK. Why might you still want to use this package?\n\n- It gives you the ability to define mapping updates of modules and Cmd/Sub mapping in single place (DRY).\n- It simplifies the routine code in functions delegating to child modules\n- Enforces common type interface for functions expressed from modules\n\n## How?\n\n**The most important type that TEA is built around is `( Model, Cmd Msg )`.\nAll we're missing is just a tiny abstraction that will make working with this\npair easier. This is really the core idea of the whole `Glue` package.**\n\nTo simplify gluing of things together, this package introduces the `Glue` type.\nThis is simply just a name-space for pure functions that defines interface\nbetween modules to which you can then refer by single name. Other functions\nwithin the `Glue` package use the `Glue.Glue` type as proxy to access these functions.\n\n\u003e Note that Glue has essentially 2 parts. The first one is simple Lens for model updates.\n\u003e The second is writer of effects (Cmds, Subscriptions).\n\n### Gluing independent TEA App\n\nThis is how we can construct the `Glue` type for [counter example](https://guide.elm-lang.org/architecture/buttons.html):\n\n```elm\nimport Glue exposing (Glue)\nimport Counter\n\ncounter : Glue Model Counter.Model Msg Counter.Msg\ncounter =\n    Glue.glue\n        { msg = CounterMsg\n        , get = .counterModel\n        , set = \\subModel model -\u003e { model | counterModel = subModel }\n        }\n```\nAll mappings from one type to another (`Model` and `Msg` of parent/child) will\nhappen as defined in this type. Definition of this interface depends on API of\nchild module (`Counter` in this case).\n\nWith `Glue` defined, we can go and integrate it to rest of the logic.\nBased on the `Glue` type definition, we know we're expecting `Model` and `Msg`\nto be (at least) as following:\n\n```elm\ntype alias Model =\n    { counterModel : Counter.Model }\n\ntype Msg\n    = CounterMsg Counter.Msg\n```\n\nNow we can define init, update and view functions:\n\n```elm\n\ninit : ( Model, Cmd Msg )\ninit =\n    ( Model, Cmd.none )\n        |\u003e Glue.init counter Counter.init\n\nupdate : Msg -\u003e Model -\u003e ( Model, Cmd Msg )\nupdate msg model =\n    case msg of\n        CounterMsg counterMsg -\u003e\n            ( model, Cmd.none )\n                |\u003e Glue.update counter Counter.update counterMsg\n\nview : Model -\u003e Html Msg\nview =\n    Glue.view counter Counter.view\n```\n\nAs you can see we're using just `Glue.init`, `Glue.update` and `Glue.view`\nin these functions to wire child module. Also compared to the original TEA example\nwe can easily update parent Model an generate additional parent Cmd as well.\n\n\u003e This version of counter is using `Browser.element` type of interface as opposed\n\u003e to `Browser.sandbox`. It is possible to use `Glue` with `sandbox` type of interface.\n\u003e See the [counter example](https://github.com/turboMaCk/glue/tree/master/examples/Counter) and the `Glue.simple` constructor.\n\n### Wrap Polymorphic Module\n\nWe're going to be using term \"polymorphic\" just because of the lack of better\nname. What we really mean is a module which already translates its inner `Msg`\nto some `a` by function provided from parent module.\n\nSuch module can also be generating parent messages using function passed into\nits functions making it possible to (asynchronously) notify parent module about\ncertain events.\n\nTo make the `Counter` example \"polymorphic\" we start by adding one extra argument\nto its `view` function and use `Html.map` internally. Then we need to change type\nannotation of `init` and `update` to `Cmd msg`. Since both function are using\njust `Cmd.none` we don't need to change anything else but that.\n\n```elm\ninit : ( Model, Cmd msg )\n\nupdate : msg -\u003e Model -\u003e ( Model, Cmd msg )\n\nview : (Msg -\u003e msg) -\u003e Model -\u003e Html msg\nview toMsg model =\n    Html.map toMsg \u003c|\n        Html.div []\n            [ Html.button [ Html.Events.onClick Decrement ] [ Html.text \"-\" ]\n            , Html.text \u003c| toString model\n            , Html.button [ Html.Events.onClick Increment ] [ Html.text \"+\" ]\n            ]\n```\n\n\u003e As you can see `view` is now taking an extra argument - function from `Msg` to parent's `msg`.\n\u003e In practice it's usually good to use record with functions called `Config msg` which will be much more extensible.\n\nNow we need to change `Glue` type definition in parent module to reflect the new API of `Counter`:\n\n```elm\ncounter : Glue Model Counter.Model Msg Msg\ncounter =\n    Glue.poly\n        { get = .counterModel\n        , set = \\subModel model -\u003e { model | counterModel = subModel }\n        }\n```\n\nAs you can see, we've switched from `Glue.glue` function to `Glue.poly`.\nAlso the type signature now contains `Msg` twice as the type produced by the\nchild module is the `Msg` of parent module. In fact `Glue.poly` is just\na constructor that defines `msg` as `identity`.\n\nWe also need to change parent's view since its API has changed and we need to\npass an extra argument now:\n\n```elm\nview : Model -\u003e Html Msg\nview =\n    Glue.view counter (Counter.view CounterMsg)\n```\n\n### Child-parent Communication\n\nIf your module is [polymorphic](#wrap-polymorphic-module) it can easily send `Cmd`s to its parent.\nPlease check [cmd-extra](http://package.elm-lang.org/packages/GlobalWebIndex/cmd-extra/latest) package\nwhich helps you construct `Cmd Msg` from `Msg`.\n\n**It's important to understand that this might not be the best technique for\nmanaging all communication between parent and child. You can always expose `Msg`\nconstructor from child (`exposing (Msg(..))`) and pattern-match on it in parent.\nIf you need to do such a thing you might have made a design mistake (improper\nseparation of state). Do these states really need to be separated? In most cases\ncommunicating with parent in async fashion makes it easier to reason about data\nflow in the app but there is no silver bullet. You know the best what best\napplies for your case.**\n\nUsing `Cmd` for communication with parent module works like this:\n\n```text\n    +------------------------------------+\n    |                                    |\n    v                                    |\n+-----------------------------------+    |\n|                                   |    |\n| Parent Module                     |    |\n|                                   |    +\n|   +                               |  Cmd Msg\n|   |                               |    |\n|  Model                            |    |\n|   |                               |    |\n|   |   +------------------------+  |    |\n|   |   |                        |  |    |\n|   |   | Child Module           |  |    |\n|   |   |                        |  |    |\n|   +-\u003e |                        +-------+\n|       +------------------------+  |\n|                                   |\n+-----------------------------------+\n\n```\n\nAs an example, we can use the (polymorphic) `Counter.elm` again.\nLet's say we want to send some action to the parent whenever its model (count) changes.\n\nFor this we need to define a helper function in `Counter.elm`:\n\n```elm\n-- this uses `GlobalWebIndex/cmd-extra`\nimport Cmd.Extra\n\nnotify : (Int -\u003e msg) -\u003e Int -\u003e Cmd msg\nnotify toMsg count =\n    Cmd.Extra.perform \u003c| toMsg count\n```\n\n`notify` takes the parent's `Msg` constructor that is expecting integer as an argument and performs it as `Cmd`.\n\nNow we need to change `init` and `update` so they're emitting this new `Cmd`.\nThe simplest way is just to make them both accept a `msg` constructor.\n\n```elm\ninit : (Int -\u003e msg) -\u003e ( Model, Cmd msg )\ninit toMsg =\n    let\n        model =\n            0\n    in\n    ( 0, notify toMsg model )\n\n\nupdate : (Int -\u003e msg) -\u003e Msg -\u003e Model -\u003e ( Model, Cmd msg )\nupdate toParentMsg msg model =\n    let\n        newModel =\n            case msg of\n                Increment -\u003e\n                    model + 1\n\n                Decrement -\u003e\n                    model - 1\n    in\n        ( newModel, notify toParentMsg newModel )\n```\n\nNow both `init` and `update` should send `Cmd` after `Model` is updated.\nThis is a breaking change to `Counter`'s API (an extra argument) so we need to change its integration as well.\nBut since we want to actually use this message and do something with it let's first update\nthe parent's `Model` and `Msg`:\n\n```elm\ntype alias Model =\n    { max : Int\n    , counter : Counter.Model\n    }\n\ntype Msg\n    = CounterMsg Counter.Msg\n    | CountChanged Int\n```\n\nBecause we've changed `Model` (added `max : Int`) we should change `init`\nand probably render max value in `view` of parent as well:\n\n```elm\ninit : ( Model, Cmd Msg )\ninit =\n    ( Model 0, Cmd.none )\n        |\u003e Glue.init counter Counter.init\n\nview : Model -\u003e Html Msg\nview model =\n    Html.div []\n        [ Glue.view counter (Counter.view CounterMsg) model\n        , Html.text \u003c| \"Max historic value: \" ++ toString model.max\n        ]\n```\n\nThis completes the changes to `Model`. Now we need to change update `update` function\nso it can handle the `CountChanged` message.\n\n```elm\nupdate : Msg -\u003e Model -\u003e ( Model, Cmd Msg )\nupdate msg model =\n    case msg of\n        CounterMsg counterMsg -\u003e\n            ( model , Cmd.none )\n                |\u003e Glue.update counter (Counter.update CountChanged) counterMsg\n\n        CountChanged num -\u003e\n            if num \u003e model.max then\n                ( { model | max = num }, Cmd.none )\n            else\n                ( model, Cmd.none )\n```\n\nAs you can see we're setting `max` to received int if it's greater than the current value.\n\nSee this [complete example](https://github.com/turboMaCk/glue/tree/master/examples/Bubbling) to learn more.\n\n## License\n\nBSD-3-Clause\n\nCopyright 2017-2019 Marek Fajkus\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fturbomack%2Fglue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fturbomack%2Fglue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fturbomack%2Fglue/lists"}