# @chaineach

**URL:** https://discourse.julialang.org/t/chaineach/134423
**Category:** General Usage
**Tags:** macros, dataframes, chain
**Created:** [December 8, 2025, 2:13am UTC](https://discourse.julialang.org/t/chaineach/134423 "2025-12-08T02:13:03Z")
**Posts on this page:** 13
**Page:** 1

<div class="post-metadata">

### Author: ![Lincoln\_Hannah](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lincoln_hannah/32/19198_2.png) [@Lincoln\_Hannah](https://discourse.julialang.org/u/Lincoln_Hannah)
#### Post date: [December 8, 2025, 2:13am UTC](https://discourse.julialang.org/t/chaineach/134423/1 "2025-12-08T02:13:03Z")

</div>

I’d like a macro @chaineach such that:

```julia-auto
@chaineach x begin
    #commands
end

```

is equivalent to

```julia-auto
map( y -> @chain y begin
    #commands
end, collect(x) )

```

Example usage is a `@chain` block processing a DataFrame and creating a GroupedDataFrame  
I’d then like to apply a `@chain` block to each subdataframe.

---

<div class="post-metadata">

### Author: ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)
#### Post date: [December 8, 2025, 2:52am UTC](https://discourse.julialang.org/t/chaineach/134423/2 "2025-12-08T02:52:30Z")

</div>

What about this:

```julia-auto
macro chaineach(x, ex)
    y = gensym()
    quote
        map($y -> @chain($y, $ex), collect($x))
    end |> esc
end

```

Example:

```julia-auto
julia> using Chain

julia> r = 1:3; x = 2; @chaineach r begin _ + x; - end
3-element Vector{Int64}:
 -3
 -4
 -5

```

The macro assumes that `@chain` is in scope.

---

<div class="post-metadata">

### Author: ![Lincoln\_Hannah](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lincoln_hannah/32/19198_2.png) [@Lincoln\_Hannah](https://discourse.julialang.org/u/Lincoln_Hannah)
#### Post date: [December 8, 2025, 3:37am UTC](https://discourse.julialang.org/t/chaineach/134423/3 "2025-12-08T03:37:49Z")

</div>

```julia-auto
using DataFrames, Chain, TidierData

macro chaineach(x, ex)
    y = gensym()
    quote
        map($y -> @chain($y, $ex), collect($x))
    end |> esc
end

@chain begin 

    DataFrame( a=[1,1,2,2], b=1:4, c=11:14 )

    @group_by a

    @chaineach _ begin
        
        sum(_.b) + sum(_.c)

    end

end 

ERROR: FieldError: type GroupedDataFrame has no field `b`, available fields: `parent`, `cols`, `groups`, `idx`, `starts`, `ends`, `ngroups`, `keymap`, `lazy_lock`

```

---

<div class="post-metadata">

### Author: ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)
#### Post date: [December 8, 2025, 3:52am UTC](https://discourse.julialang.org/t/chaineach/134423/4 "2025-12-08T03:52:40Z")

</div>

I’m not sure if you can expect nested `@chain` / `@chaineach` macros to work. What do `@macroexpand` and `@macroexpand1` give?

EDIT: This works

```julia-auto
julia> @chaineach [1:2, 3:4] begin @chain _ begin 2*_ end end
2-element Vector{StepRangeLen{Int64, Int64, Int64, Int64}}:
 2:2:4
 6:2:8

```

but this doesn’t:

```julia-auto
julia> @chain [1:2, 3:4] begin @chaineach _ begin 2*_ end end
ERROR: MethodError: no method matching *(::StepRangeLen{Int64, Int64, Int64, Int64}, ::Vector{UnitRange{Int64}})

```

Could it be that `@chain` recognizes itself when scanning an expression? It cannot recognize `@chaineach`.

---

<div class="post-metadata">

### Author: ![Lincoln\_Hannah](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lincoln_hannah/32/19198_2.png) [@Lincoln\_Hannah](https://discourse.julialang.org/u/Lincoln_Hannah)
#### Post date: [December 8, 2025, 5:00am UTC](https://discourse.julialang.org/t/chaineach/134423/5 "2025-12-08T05:00:12Z")

</div>

I think the @chain macro is coded specifically with nesting ability  
I guess the same would have to be added specifically for @chaineach

---

<div class="post-metadata">

### Author: ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)
#### Post date: [December 8, 2025, 1:20pm UTC](https://discourse.julialang.org/t/chaineach/134423/6 "2025-12-08T13:20:38Z")

</div>

Idea: replace `@chaineach` by `@map @chain`. This way an outer `@chain` can see the inner one and act accordingly.

```julia-auto
macro map(ex)
    y = gensym()
    x, ex.args[3] = ex.args[3], y
    quote
        map($y -> $ex, collect($x))
    end |> esc
end

```

Nesting seems to work:

```julia-auto
julia> @map @chain [1:2, 3:4] begin 2*_ end
2-element Vector{StepRangeLen{Int64, Int64, Int64, Int64}}:
 2:2:4
 6:2:8

julia> @map @chain [1:2, 3:4] begin @chain _ begin 2*_ end end
2-element Vector{StepRangeLen{Int64, Int64, Int64, Int64}}:
 2:2:4
 6:2:8

julia> @chain [1:2, 3:4] begin @map @chain _ begin 2*_ end end
2-element Vector{StepRangeLen{Int64, Int64, Int64, Int64}}:
 2:2:4
 6:2:8

julia> @map @chain [1:2, 3:4] begin @map @chain _ begin 2*_ end end
2-element Vector{Vector{Int64}}:
 [2, 4]
 [6, 8]

```

---

<div class="post-metadata">

### Author: ![pdeffebach](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/pdeffebach/32/10320_2.png) [@pdeffebach](https://discourse.julialang.org/u/pdeffebach)
#### Post date: [December 8, 2025, 2:53pm UTC](https://discourse.julialang.org/t/chaineach/134423/7 "2025-12-08T14:53:02Z")

</div>

Basically you want an easier way to write

```julia-auto
map(x) do xi
    @chain xi begin 
        ...
    end
end

```

?

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [December 8, 2025, 4:33pm UTC](https://discourse.julialang.org/t/chaineach/134423/8 "2025-12-08T16:33:27Z")

</div>

FWIW, that’s how the @Lincoln_Hannah’s example from above would look with DataPipes.jl:

```julia-auto
       @p let
           StructArray( a=[1,1,2,2], b=1:4, c=11:14 )
           group(_.a)
           map() do __
               sum(__.b) + sum(__.c)
           end
       end

```

No new macros at all, and quite intuitive behavior: `__` always means the result of the previous pipeline step, and doing `map() do__` effectively assigns to it – starting the inner pipeline with this value.

---

<div class="post-metadata">

### Author: ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)
#### Post date: [December 8, 2025, 7:02pm UTC](https://discourse.julialang.org/t/chaineach/134423/9 "2025-12-08T19:02:01Z")

</div>

@aplavin, using DataFrames.jl, I believe the following is equivalent to your code:

```julia-auto
using DataPipes, DataFrames

@p let
    DataFrame(a=[1,1,2,2], b=1:4, c=11:14)
    groupby(__, :a)
    combine() do __
        sum(__.b) + sum(__.c)
    end
end

```

---

<div class="post-metadata">

### Author: ![pdeffebach](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/pdeffebach/32/10320_2.png) [@pdeffebach](https://discourse.julialang.org/u/pdeffebach)
#### Post date: [December 8, 2025, 8:55pm UTC](https://discourse.julialang.org/t/chaineach/134423/10 "2025-12-08T20:55:05Z")

</div>

One wrinkle here is that `map` is not defined for grouped data frames, so this particular example wouldn’t quite work. But overall I think this macro solves your problem

```julia-auto
julia> macro chaineach(iterable, chainarg)
           map_arg = gensym()
           chainblock = Expr(:macrocall, Symbol("@chain"), 1, map_arg, chainarg)
           out = quote 
               map($iterable) do $map_arg
                   $chainblock
               end
           end
           return esc(out)
       end;

julia> x = [1, 2, 3];

julia> @chaineach x begin
           _ + 1
       end
3-element Vector{Int64}:
 2
 3
 4

julia> df = DataFrame(g = [1, 1, 2, 2], y = [1, 2, 10, 20]);

julia> gd = groupby(df, :g);

julia> gd_vec = [gdi for gdi in gd];

julia> @chaineach gd_vec begin 
           @with begin 
               sum(:y)
           end
       end
2-element Vector{Int64}:
  3
 30

```

---

<div class="post-metadata">

### Author: ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)
#### Post date: [December 8, 2025, 9:36pm UTC](https://discourse.julialang.org/t/chaineach/134423/11 "2025-12-08T21:36:37Z")

</div>

> [@pdeffebach](#):
>
> this macro solves your problem

Isn’t it essentially identical to the macro in my first response (modulo the `collect` that OP wanted to have)?

---

<div class="post-metadata">

### Author: ![pdeffebach](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/pdeffebach/32/10320_2.png) [@pdeffebach](https://discourse.julialang.org/u/pdeffebach)
#### Post date: [December 8, 2025, 9:50pm UTC](https://discourse.julialang.org/t/chaineach/134423/12 "2025-12-08T21:50:09Z")

</div>

Ah you are correct. And your map solution is pretty good.

---

<div class="post-metadata">

### Author: ![Lincoln\_Hannah](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lincoln_hannah/32/19198_2.png) [@Lincoln\_Hannah](https://discourse.julialang.org/u/Lincoln_Hannah)
#### Post date: [December 9, 2025, 12:10am UTC](https://discourse.julialang.org/t/chaineach/134423/13 "2025-12-09T00:10:13Z")

</div>

I love this syntax. I’m converting tabular historical data to a KeyedArray of vol surfaces.

```julia-auto
using DataFrames, Chain, TidierData, AxisKeys

@chain begin

    # Many lines to get date
    @select histDate volatility money tenor
    @arrange histDate days money
    @group_by histDate

    @aside histDate = first.(keys(_))
    
    @map @chain _ begin

        wrapdims( :volatility, :money, :tenor )
        extend_surface()
        Vol_Surface{linear_variance}()

    end 

    KeyedArray( histDate )

end

```

@pdeffebach your solution of a single macro @chaineach is great too. I assume the the `collect()` function that @matthias314 added could be included so it could work directly on a `GroupedDataFrame`. Either way its a very clean syntax. Minimal brackets, minimal dummy variables.
