# Fixing the Piping/Chaining Issue

**URL:** <https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654>\
**Category:** Internals & Design\
**Tags:** proposal, piping, chaining, partial-evaluation, threading\
**Created:** [November 2, 2022, 11:04am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654 "2022-11-02T11:04:36Z")\
**Posts on this page:** 1\
**Showing post:** 98

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [November 7, 2022, 6:37am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654/98 "2022-11-07T06:37:17Z")

</div>

Wow `@generated` is like having a superpower in your back pocket. Thanks @CameronBieganek for that!

Also: its performance is great. I think I was mistaken previously.

> **Third-pass Code for a typed arbitrary-index partial application functor**
>
> ```julia
> struct Fix{F,fixinds,V<:Tuple,KW<:NamedTuple}
> f::F
> fixvals::V
> fixkwargs::KW
> 
> Fix{F,fixinds,V,KW}(f,fixvals,fixkwargs) where {F,fixinds,V,KW} = begin
> orderok(a, b) = a < b || (a > 0 && b < 0) # not a perfect test ... just want args ordered left to right
> length(fixinds) > 1 && @assert all(orderok.(fixinds[begin:end-1], fixinds[begin+1:end]))
> new{F,fixinds,V,KW}(f,fixvals,fixkwargs)
> end
> end
> 
> Fix{fixinds}(f, fixvals; fixkwargs...) where {fixinds} = 
> Fix{typeof(f), fixinds, typeof(fixvals), typeof((; fixkwargs...))}(f, fixvals, (; fixkwargs...))
> 
> @generated (f::Fix{F,fixinds,V,KW})(args...; kwargs...) where {F,fixinds,V,KW} = begin
> combined_args = Vector{Expr}(undef, length(fixinds)+length(args))
> args_i = fixed_args_i = 1
> for i ∈ eachindex(combined_args)
> if any(==(fixinds[fixed_args_i]), (i, i-length(combined_args)-1))
> combined_args[i] = :(f.fixvals[$fixed_args_i])
> fixed_args_i = clamp(fixed_args_i+1, eachindex(fixinds))
> else
> combined_args[i] = :(args[$args_i])
> args_i += 1
> end
> end
> :(f.f($(combined_args...); kwargs..., f.fixkwargs...))
> end
> 
> ```

Performance:

```julia
julia> @btime Fix{(1,2,-3,-1)}((args...;kwargs...)->(args...,(;kwargs...)), (:a,:b,:getting_close,:END), (z=5))(:y, 1, 2; k=2)
  1.000 ns (0 allocations: 0 bytes)
(:a, :b, :y, 1, :getting_close, 2, :END, (k = 2, z = 5))

```

Notes:

- call `Fix{fixinds::Tuple}(f, fixvals::Tuple; fixkwargs...)` to construct functor  
– `fixinds` is a tuple of indices starting from left (e.g. `(1, 2, 3)`), and any indices counting from the right are negative (e.g., `(1, 2, 3, -3, -2, -1)`). Index `1` is left-most argument, `-1` is right-most.
- Fixed keyword arguments override called keyword arguments. Not sure if this is the right decision.
- There is no check that the number of arguments or keyword arguments fit a profile; the combined argument list simply grows with number of arguments passed during call, with new arguments filling in the middle between the arguments with positive indices and the arguments with negative indices.
- This could use some more road testing, for sure
- `FixFirst(f,x)` is created by `Fix{(1,)}(f, (x,))` which `isa Fix{<:Any, (1,)}`. It is presumed that such an object would be created by `f(x, _...)`.
- `FixLast(f,x)` is created by `Fix{(-1,)}(f, (x,))` which `isa Fix{<:Any, (-1,)}`. It is presumed that such an object would be created by `f(_..., x)`.
- In many locations where `Base.Fix2` is used, people will probably use `f(_, x)`, which will create a `Fix{<:Any, (2,)}` object. When calling a function with two arguments, the fact that `Fix{<:Any, (2,)}` behaves as `Fix{<:Any,(-1,)}` means the type signature of a partial function which does the intended task is not unique. For the people who care about the types of the object, not sure if this matters.

---

_[View the full topic](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654)._
