{"id":24395309,"url":"https://github.com/heinrichapfelmus/optimize-monad-trans","last_synced_at":"2025-04-11T15:03:47.846Z","repository":{"id":13681646,"uuid":"16375395","full_name":"HeinrichApfelmus/optimize-monad-trans","owner":"HeinrichApfelmus","description":"Toy example — How can GHC optimize monad transformers?","archived":false,"fork":false,"pushed_at":"2016-11-08T11:41:35.000Z","size":235,"stargazers_count":11,"open_issues_count":2,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-25T11:04:20.821Z","etag":null,"topics":["compiler-optimization","explanatory","haskell","monad-transformers"],"latest_commit_sha":null,"homepage":null,"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/HeinrichApfelmus.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":"2014-01-30T11:22:40.000Z","updated_at":"2019-12-25T08:20:11.000Z","dependencies_parsed_at":"2022-07-30T15:38:48.653Z","dependency_job_id":null,"html_url":"https://github.com/HeinrichApfelmus/optimize-monad-trans","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HeinrichApfelmus%2Foptimize-monad-trans","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HeinrichApfelmus%2Foptimize-monad-trans/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HeinrichApfelmus%2Foptimize-monad-trans/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HeinrichApfelmus%2Foptimize-monad-trans/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/HeinrichApfelmus","download_url":"https://codeload.github.com/HeinrichApfelmus/optimize-monad-trans/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248428957,"owners_count":21101779,"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":["compiler-optimization","explanatory","haskell","monad-transformers"],"created_at":"2025-01-19T20:27:04.286Z","updated_at":"2025-04-11T15:03:47.742Z","avatar_url":"https://github.com/HeinrichApfelmus.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"This is a toy problem to investigate how the [Glasgow Haskell Compiler][ghc] can optimize a [monad transformer stack][transformers].\n\n  [transformers]: http://hackage.haskell.org/package/transformers\n  [ghc]: http://www.haskell.org/ghc/docs/latest/html/users_guide/index.html\n\n[Discussion on reddit](http://www.reddit.com/r/haskell/comments/1wgk8r/frp_release_of_reactivebanana_version_08/cf2faou).\n\nOverview\n--------\n\nConsider the generic problem of traversing some data structure using a monad. Think [`Data.Traversable`][traversable]. The traversal is usually coded as a recursive loop, like this\n\n    go :: Structure -\u003e Monad A \n    go x = ... go x' ...\n\nThe `Monad` is usually a function type, for instance a state monad\n\n    type Monad a = S -\u003e (a, S)\n\nThe `go` function looks like a recursive definition of a closure / function, but that's not what we want. Instead, what we want is that the state monad is inlined into the definition of `go`, so that it becomes a tight inner loop. In other words, we are not interested in the result `Monad A` as a first class value, we just want to use the monad to organize our traversal.\n\n  [traversable]: http://hackage.haskell.org/package/base-4.6.0.1/docs/Data-Traversable.html\n\n\nExample \u0026 Analysis\n------------------\n\nThe module [`OptimizeMonadTrans`](OptimizeMonadTrans.hs) implements a traversal\n\n    traverseTree :: Monad m =\u003e (a -\u003e m [a]) -\u003e a -\u003e m ()\n\nwhich is then applied to a tree\n\n    evaluate :: Tree -\u003e Eval [Tree]\n\nThe monad `Eval` is a simple `ReaderT` monad transformer on top of `IO`. The reader monad should be the simplest case to optimize for the compiler. The goal is to have `ghc -O` inline the monad to the point that the traversal becomes a tight inner loop.\n\nDifferent implementations for the `Eval` monad can yield different results:\n\n* The module [`Eval1`](Eval1.hs) implementes a two-level `ReaderT` transformer. The resulting core [`OptimizeMonadTrans.core1.hs`](results/OptimizeMonadTrans.core1.hs) is not good, it allocates closures. It looks roughly like this:\n\n        traverseExample :: [Tree] -\u003e Eval1.Value -\u003e ReaderT Eval1.Value IO ()\n        traverseExample = \\tree valueOuter -\u003e\n            case tree of _ {\n                []     -\u003e return';\n                (x:xs) -\u003e \n                    let m_Xim :: ReaderT Eval1.Value IO [Tree]\n                        m_Xim = case x of _ {\n                                OptimizeMonadTrans.Branch action children -\u003e ... \n                            }\n                    in\n                        \\(valueInner :: Eval1.value)\n                         (s :: GHC.Prim.State# GHC.Prim.RealWorld) -\u003e\n                            case m_Xim valueInner s of _\n                                { ... traverseExample ... };\n            }\n\n        \n        return' :: Eval1.Value -\u003e GHC.Prim.State# GHC.Prim.RealWorld\n                -\u003e (# GHC.Prim.State# GHC.Prim.RealWorld, () #)\n        return' = \\_ s -\u003e (# s, () #)\n\n    The program performs a case analysis on the list of trees after binding the outer reader value to `valueOuter`, but then it allocates an action `m_Xim` for the inner reader and builds a closure that essentially just calls `m_Xim`. \n\n* The module [`Eval2`](Eval2.hs) uses a single level `ReaderT`. The resulting core [`OptimizeMonadTrans.core2.hs`](results/OptimizeMonadTrans.core2.hs) is very good, the traversal is inlined into a tight inner loop. It looks roughly like this:\n\n        traverseExample :: [Tree] -\u003e (Eval2.Value,Eval2.Value)\n            -\u003e GHC.Prim.State# GHC.Prim.RealWorld\n            -\u003e (# GHC.Prim.State# GHC.Prim.RealWorld, () #)\n        traverseExample = \\tree value s -\u003e\n            case tree of _ {\n              []     -\u003e (# s, () #);\n              (x:xs) -\u003e case x of _ {\n                  OptimizeMonadTrans.Branch action_aAv children_aAw -\u003e ... ;\n                  ...\n              }\n        \n    After taking both the reader value and GHC's internal state token for the IO monad as arguments, the program continues a case analysis on the list of trees. No other closures are build and no monadic action is shared with a `let` binding.\n\nThe first variant `Eval1` is more modular, as it uses a stack of monad transformers, while the second variant `Eval2` yields much faster code. Is it possible to add INLINE or other compiler annotations to have the first variant behave like the second?\n\nUnfortunately, the answer is **\"no\"**. The variant `Eval3` reveals why. Defining a reader monad with two arguments,\n    \n    newtype Eval a = E { run :: Value -\u003e Value -\u003e IO a }\n\nthe stack of monad transformers from variant `Eval1` is equivalent to the monad bind\n\n    m \u003e\u003e= k  = E $ \\x -\u003e let b = run m x in \\y -\u003e b y \u003e\u003e= \\a -\u003e run (k a) x y\n\nwhile the single argument from variant `Eval2` is equivalent to\n\n    m \u003e\u003e= k  = E $ \\x y -\u003e run m x y \u003e\u003e= \\a -\u003e run (k a) x y\n\nThe difference is slight but important: in the first variant, the computation `run m x` i shared over several values for `y`, while in the second variant, the computation `run m x` is recomputed for every invocation with a value `y`. Since this computation could have been very expensive, rightfully GHC refuses to inline the first variant.\n\nNote that the same reasoning could be applied to the state token for the `IO` monad. Why doesn't GHC build closures for that? The answer is that the compiler treats the `State#` type constructor as a special case and doesn't refrain from recomputing `IO` actions. This is GHC's infamous [state hack][].\n\n  [state hack]: https://ghc.haskell.org/trac/ghc/ticket/1168\n\n\nBuilding\n--------\n\nA [`Makefile`](Makefile) helps with compiling. The commands\n\n    $ make core1\n    $ make core2\n    $ make core3\n    $ make core3s\n\nwill compile the main module using the different variants and output the corresponding GHC core to the [`results`](results/) folder.\n\nThere are two more exotic variants as well. Building with\n\n    $ make coreImplicit\n\nwill use GHCs `ImplicitParameters` extension to implement the reader monad transformer, while\n\n    $ make coreRefl\n\nuses [type class reflection][reflection] to implement the monad transformer, both in the hope of circumventing the issue with sharing by moving the reader arguments into the type system. However, as the core demonstrates, neither implementation succeeds.\n\n  [reflection]: http://hackage.haskell.org/package/reflection\n\nResources\n---------\n\nGHC documentation\n\n* [How to read GHC Core](http://www.haskell.org/ghc/docs/7.6.3/html/users_guide/options-debugging.html#idp39365504). Reading core may take a little time to get used to, but it's actually less painful than it looks at first glance. The main clutter is actually type signatures, which can be ignored for the most part.\n* [INLINE and INLINEABLE pragmas](http://www.haskell.org/ghc/docs/7.6.3/html/users_guide/pragmas.html#inline-noinline-pragma). Note that INLINE inlines the original right-hand side, not an optimized one.\n* [SPECIALIZE pragma](http://www.haskell.org/ghc/docs/7.6.3/html/users_guide/pragmas.html#specialize-pragma). Specializes polymorphic types. This doesn't seem to be a problem in our case, though.\n* [RULES pragma](http://www.haskell.org/ghc/docs/7.6.3/html/users_guide/rewrite-rules.html). Rewrite rules, the big gun. Not sure whether they apply in this situation, though, because everything should be solveable by inlining. However, the [CONLIKE pragma](http://www.haskell.org/ghc/docs/7.6.3/html/users_guide/rewrite-rules.html#conlike) seems interesting for reducing `let` statements, though it only applies to rules.\n\nGeneral information on improving running times of Haskell programs\n\n* Stackoverflow — [Tools for analyzing performance of a Haskell program][tools]\n* Haskellwiki — [Performance tips for Monads][wiki]. Seems somewhat uninformed. (Jan 2014)\n\n  [wiki]: http://www.haskell.org/haskellwiki/Performance/Monads\n  [tools]: http://stackoverflow.com/questions/3276240/tools-for-analyzing-performance-of-a-haskell-program\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheinrichapfelmus%2Foptimize-monad-trans","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fheinrichapfelmus%2Foptimize-monad-trans","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheinrichapfelmus%2Foptimize-monad-trans/lists"}