{"id":30136655,"url":"https://github.com/alecloudenback/mapunroll.jl","last_synced_at":"2025-08-10T23:10:49.854Z","repository":{"id":304359933,"uuid":"1018566227","full_name":"alecloudenback/MapUnroll.jl","owner":"alecloudenback","description":null,"archived":false,"fork":false,"pushed_at":"2025-08-02T05:15:17.000Z","size":237,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-02T06:24:15.718Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alecloudenback.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-12T14:37:32.000Z","updated_at":"2025-07-16T10:31:13.000Z","dependencies_parsed_at":"2025-08-02T05:28:43.489Z","dependency_job_id":null,"html_url":"https://github.com/alecloudenback/MapUnroll.jl","commit_stats":null,"previous_names":["alecloudenback/mapunroll","alecloudenback/mapunroll.jl"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/alecloudenback/MapUnroll.jl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecloudenback%2FMapUnroll.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecloudenback%2FMapUnroll.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecloudenback%2FMapUnroll.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecloudenback%2FMapUnroll.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alecloudenback","download_url":"https://codeload.github.com/alecloudenback/MapUnroll.jl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecloudenback%2FMapUnroll.jl/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269803855,"owners_count":24477658,"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-10T02:00:08.965Z","response_time":71,"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":[],"created_at":"2025-08-10T23:10:49.352Z","updated_at":"2025-08-10T23:10:49.841Z","avatar_url":"https://github.com/alecloudenback.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MapUnroll.jl\n\n[![Build Status](https://github.com/alecloudenback/MapUnroll.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/alecloudenback/MapUnroll.jl/actions/workflows/CI.yml?query=branch%3Amain)\n[![Coverage](https://codecov.io/gh/alecloudenback/MapUnroll.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/alecloudenback/MapUnroll.jl)\n\n`MapUnroll.jl` provides the `@unroll` macro to help write performant, type-stable, and order-dependent loops without needing to manually define the output container.\n\n## The Problem: Stateful `map` Operations\n\nConsider a simulation where each step depends on the result of the previous one. A naive implementation using `map` might look like this:\n\n```julia\nfunction simulate(n)\n    x = 0.0\n\n    map(1:n) do i\n        x += exp(i)\n        (timestep=i, state=x)\n    end\nend\n```\n\nThis pattern has two significant issues:\n\n1.  **Performance:** The variable `x` is closed over and \"boxed\" (wrapped in a mutable container) by the compiler, leading to type instability and poor performance.\n2.  **Correctness:** `map` does not guarantee sequential execution. For a stateful calculation like this, the order of operations is critical, meaning `map` could produce an incorrect result.\n\n## The Solution: `@unroll`\n\n`MapUnroll.jl` solves both problems by combining the guaranteed execution order of a `for` loop with automatic output type inference.\n\nTo fix the `simulate` function, we use the `@unroll` macro and utilities re-exported for convenience from `BangBang.jl` and `MicroCollections.jl`.\n\n```julia\nusing MapUnroll\n\nfunction simulate_unroll(n)\n    out = UndefVector{Union{}}(n)\n    x = 0.0\n\n    @unroll 2 for i ∈ 1:n\n        x += exp(i)\n        out = setindex!!(out, (timestep=i, state=x), i)\n    end\n    out\nend\n```\n\n### How it Works\n\nThe `@unroll` macro \"unrolls\" the first few iterations of the loop (default is 2). This allows the Julia compiler to observe the type of the object being created.\n\n1.  From the first iteration, the compiler infers the concrete `eltype` of the output.\n2.  `setindex!!` then creates a new output container (`Vector`) with that specific `eltype`.\n3.  The rest of the loop populates the new, type-stable vector.\n\nThis avoids the boxing and performance issues of the `map` approach, while the `for` loop ensures correctness by executing in sequence.\n\n### Performance Comparison\n\nThe `@unroll` version avoids the `Core.Box` allocation and is significantly faster.\n\n**Original `simulate`:**\n```julia-repl\njulia\u003e @code_warntype simulate(100)\n...\nLocals\n  #15::var\"#15#16\"\n  x::Core.Box\nBody::Vector\n1 ─       (x = Core.Box())\n...\n```\n```julia-repl\njulia\u003e using BenchmarkTools\njulia\u003e @btime simulate(100)\n  9.167 μs (407 allocations: 11.12 KiB)\n```\n\n**`simulate_unroll` with MapUnroll.jl:**\n```julia-repl\njulia\u003e @btime simulate_unroll(100)\n  233.583 ns (2 allocations: 1.62 KiB)\n```\n\n## Comparison with Other Approaches\n\n| Method | Performance | Correctness (Order) | When to Use |\n| :--- | :--- | :--- | :--- |\n| **`map` with closure** | Poor (boxing) | No | Not recommended for stateful loops. |\n| **`map` with `Ref`** | Good | No | When order doesn't matter but you need to mutate a value. |\n| **`accumulate`** | Good | Yes | An excellent, idiomatic choice for this specific simulation pattern, but can get verbose and unweildy when the current state gets complex. |\n| **`@unroll` (this package)** | Good | Yes | For developers who prefer an explicit `for` loop, or for complex loop bodies where `accumulate` is less natural. |\n\nWhile using a `Ref(x)` can solve the boxing problem, it does not solve the execution order problem with `map`. For stateful patterns, idiomatic functional approaches like `accumulate` are also a great option. `@unroll` provides a general-purpose tool that gives the developer control over the loop structure while delegating the tedious parts of output container creation to the compiler.\n\n## Credit\n\nThe original `@unroll` macro was developed by [Mason Protter](https://github.com/MasonProtter).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falecloudenback%2Fmapunroll.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falecloudenback%2Fmapunroll.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falecloudenback%2Fmapunroll.jl/lists"}