# Fixing the Piping/Chaining/Partial Application Issue (Rev 2)

**URL:** <https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408>\
**Category:** Internals & Design\
**Tags:** proposal, piping, chaining, partial-evaluation, threading\
**Created:** [November 17, 2022, 3:36pm UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408 "2022-11-17T15:36:57Z")\
**Posts on this page:** 20\
**Page:** 1

<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 17, 2022, 3:36pm UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/1 "2022-11-17T15:36:57Z")

</div>

Starting this thread anew to avoid confusion, because I changed my mind on which approach I support partway through a long thread (sorry!). It’s also useful to distill some of the last thread’s learnings.

Thank you to all who participated—especially those who rightfully criticized my ignorance! Many issues are matters of genetic algorithm or simulated annealing, and ~~ossified~~ entrenched problems aren’t solved without some heat.

Please grab a cup of coffee, pull up a chair, and bring a fresh pair of eyes to consider this (refreshed!) proposal. Or for the impatient, skip to the demo at the end and start playing with it.

![JóreggeltGIF](https://global.discourse-cdn.com/julialang/original/3X/5/1/51fefb75b1664235d1919bb9ec43624ff33fd872.gif)

# Objective

* * *

To solve Julia’s piping/chaining/currying/partial application problem with an elegant and functional approach worthy and idiomatic of Base Julia (i.e., not constrained to macro calls).

# Background

* * *

See [the previous proposal](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654) for background information.

To summarize why I changed my mind:

> [@Fixing the Piping/Chaining Issue](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654/176):
>
> My thought however, as the two approaches are redundant, both require parsing changes, and both have their own learning curves, is that it’s likely difficult to advocate for both simultaneously.
> 
> So if I have to pick one, I am compelled to choose the one that’s more generally useful and whose behavior is more obvious.

# Proposal

* * *

I am making essentially two proposals: 1.) partial function application syntax, and 2.) call chaining syntax.

## PAS

I now support [PR #24990](https://github.com/JuliaLang/julia/pull/24990) with some minor modifications. #24990 is Scala-style “tight-currying” underscore syntax. I will refer to this as “Partial Application Syntax,” although my informal name for it is “basically \*the\* perfect syntax for partial application.” Quoting @stevengj from the PR, with my modifications _in italics_:

1. The currying is “tight”, i.e. it only converts the immediately surrounding function call into a ~~lambda~~ _partial application functor_, [as suggested by](https://github.com/JuliaLang/julia/issues/22710#issuecomment-314259250) [@JeffBezanson](https://github.com/JeffBezanson) (and [as in Scala](https://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#placeholder-syntax-for-anonymous-functions)). So, e.g. `f(g(_,y))` ~~is equivalent to~~ _behaves like_ `f(x -> g(x,y))`. (Note that something like `find(!(_ in c), y)` will work fine, because the `!` operator works on functions; you can also use `_ ∉ c`.) Any other rule seems hard to make comprehensible and consistent.

2. Similar to Scala, multiple underscores are converted into multiple arguments in the order they appear. e.g. `f(_,y,_)` ~~is equivalent to~~ _behaves like_ `(x,z) -> f(x,y,z)` .

3. _A slurping underscore is converted into varargs, for example_ `f(a,_...,z)` _behaves like_  
`(args...)->f(a,args...,z)`. _(note that only one slurp is allowed per expression)_

4. _Use_ `.` _to denote broadcasting, e.g._ `f.(x,_,z)` _is equivalent to_ `broadcast(f,x,_,z)`, _and_ `_.^2` _is equivalent to_ `broadcast(^, _, 2)`.

5. _For the special case of denoting an expression that will become a zero-argument function due to fixing all arguments, use_ `&_`. _For example,_ `g=f(x,y,&_); g()`  
_Note: I have no preference for the specific character sequence; I choose it because it parses._

6. _The object returned by the underscore syntax is a parametrically-typed_ `Fix` _partial application functor which allows for type inference, dispatch, and pretty-printing. For example,_ `f(x,_,z)` _is equivalent to_ `Fix{(1,3),3}(f, x, z)`. _This resolves [#36181](https://github.com/JuliaLang/julia/issues/36181). See “Demo” for sample code._

I also add a proposal specific to chaining:

## CCS

A function chaining syntax, which I will call “Call Chain Syntax,” which gives preferred treatment to PAS (the syntax, not the functor object) by suppressing applicator construction using syntax transforms for better compile time and by splatting automatically, and which also allows for “chain glue expressions” using a local keyword `it` (this will become more clear later).

1. _The infix character sequence_ `--` _, with the same “operator precedence” as property getting_ `.` _dot (17 as of writing), which takes on its R.H.S. either a callable object, or an expression of_ `it`, _or a_ `:block` _of callable objects inclusive-or expressions of_ `it`, _and calls them sequentially. For example:_  
`x--f` _is equiv to_ `let it=x; it=f(it) end`,  
`x--f(_,y)` _is equiv to_ `let it=x; it=f(it,y) end`,  
`y--f.(x,_)` _is equiv to_ `let it=y; it=f.(x,it) end`,  
`x--f(_^2,_).a[2]` _is equiv to_ `(let it=x; it=f(_^2,it) end).a[2]`,  
`x--(f; it^2+it; it/5)` _is equiv to_  
`let it=x; it=f(it); it=it^2+it; it=it/5 end`, _and_  
`x--begin f; g; h end` _is equiv to_ `x--f--g--h`,  
_which is equiv to_ `x--(f; g; h)`, _which is equiv to_  
`let it=x; it=f(it); it=g(it); it=h(it) end`.  
_Note: I choose_ `--` _because it looks neat, is easy to type, and is currently invalid syntax so it won’t be piracy to claim it._  
_Note: Expressions are considered to be expressions of_ `it` _if they have it anywhere, \*unless\* it exists only locally within nested call chains._

2. _(This should satisfy Chain.jl users:) Within the scope of a R.H.S. expression, a keyword_ `it` _is defined locally, representing the result of executing the previous element in the chain. Expressions of_ `it` _capture everything that has tighter binding than the block expression delimiter. For example:_  
`x--(f; it.a+it.b; g)` _is equiv to_  
`let it=x; it=f(it); it=it.a+it.b; it=g(it) end`.  
_Note: I prefer a keyword like_ `it` _to avoid confusion with the partial function placeholder_ `_`, _as they have distinct meanings. Using_ `it` _leverages the fact that the English language already uses the pronoun “it” for the exact same purpose of method chaining._

3. _If the next expression is a PAS expression with multiple_ `_` _underscores, then the previous expression will be splatted in._  
`x--(f; g(_,y,_...))` _is equiv to_  
`let it=x; it=f(it); it=g(it[1],y,it[2:end]...) end`.

4. _To broadcast on a callable object or an expression of_ `it` _, or a_ `:block` _of such, use_ `.--`. _For example,_  
`x.--(f; it.a[1])` _evaluates like_  
`let it=x; it=f.(it); it=(it->it.a[1]).(it) end`.

5. _A callable type_ `Base.ChainLink`, representing a sequence of operations.

6. _If_ `--` or `.--` _is “headless”, i.e., prefix, having no object on its L.H.S., then it behaves as a constructor for a_ `Base.ChainLink` _with the aforementioned behaviors (notably w.r.t. splatting and the_ `it` _keyword). For example, if we let_  
`foo = --(f; it.a+it.b; g)`, _this gives a_ `Base.ChainLink` _object that can be called like_  
`it -> begin it=f(it); it=it.a+it.b; it=g(it) end`. _Then,_  
`x--foo` _is equiv to_ `foo(x)`, _which evaluates like the example from #8_.  
_Similarly, a broadcasting chainlink can be defined by:_  
`baz = .--(f; it.a+it.b; g)`, _which calls like_  
`it -> begin it=f.(it); it=(it->it.a+it.b).(it); it=g.(it) end`.  
_Because_ `Base.ChainLink` _is a callable object, it can also be inserted into other chainlinks. For example:_  
`bar = --(h; foo); y--bar`.

7. _For Chain.jl users: an_ `@aside` _macro is not required. For example:_.  
`x--(f; (println(it); it); g)` _yields_ `g(f(x))` _and prints_ `f(x)`.

8. _(Stretch Goal) Special handling for the_ `do` _statement, when taking_ `--` _as an “argument name.” Example:_  
`map(x) do -- f; it + it^2; g end` _would be equiv to_  
`map(--(f; it + it^2; g); x)`

## [Some] Details

* * *

### Fix Functor

Latest edition of code for a generalized `Fix` partial application functor is in “Demo.”

This `Fix` functor type generalizes the behavior of `Base.Fix1` and `Base.Fix2` to fix any number of arguments in any position, for functions with fixed or variable argument lengths, and keyword arguments. With this functor, the behavior of `Base.Fix1` and `Base.Fix2` as currently defined for 2-argument functions is captured by the parametric types:

```julia
const Fix1{F,X} = Fix{F,(1,),2,Tuple{X},NamedTuple{(),Tuple{}}}
const Fix2{F,Y} = Fix{F,(2,),2,Tuple{Y},NamedTuple{(),Tuple{}}}

```

It’s also easy to make functors accepting variable argument lengths:

```julia
const FixFirst{F,X} = Fix{F,(1,),0,Tuple{X}}
const FixLast{F,X} = Fix{F,(-1,),0,Tuple{X}}

```

as well as any other argument position and count.

To call such an object without syntax sugar:

```julia
[1,2,3] |> Fix1(map, Fix2(^,2))
[1,2,3] |> FixLast(join, ", ")
[1,2,3] |> Fix{(1,2),3}(mapreduce, Fix2(^, 2), +)

```

Using underscore and chaining syntax:

```julia
[1,2,3] -- map(_^2, _)
[1,2,3] -- join(_..., ", ")
[1,2,3] -- mapreduce(_^2, +, _)

```

### Modified Underscore syntax

In alignment with @jeff.bezanson and @stevengj, I think the “tight currying” approach inspired by Scala, where `_` is used strictly for partial function evaluation (and not for constructing arbitrary lambdas), is the only acceptable approach for implementing underscore syntax as a language feature—that is to say, it’s sufficiently generic, straightforward to reason about, and useful to justify incorporation into the language proper.

Furthermore, considering how frequently partial application is employed, it’s sensible to add syntax sugar for it (as we already have for lambdas).

The modifications I propose to the original syntax of #24990 are simple: `_...` for slurping varargs, `&_` for the special case of creating a zero-argument functor, and the expected broadcasting behavior using `.`. With these additions, underscore syntax becomes fully general, able to create partial (and full) applicators fixing any number of arguments in any position for any function that the language permits.

### Call Chain Syntax: `--`

The situations where it isn’t needed to compile the functor are those occasions when it would be defined and immediately called and discarded, i.e., when using it as part of a call chain. The proposed call chain syntax `--` would invoke a syntax transformation such that the constructor and functor of the `Fix` partial function applicator needn’t be compiled, enhancing the utility of underscore syntax.

In addition, the chaining syntax allows for tuples of arguments to be “splatted” into whichever argument positions are desired, as directed by underscore syntax.

Call chain syntax has been specified to operate with the same precedence as `.` property getting. This is so that, for example, a chain like this:

```julia
"1"--parse(Int,_) == 1

```

is evaluated as

```julia
parse(Int,"1") == 1

```

instead of

```julia
(parse(Int,_) == 1)("1")

```

Importantly, it’s so that the result of a chain can have its properties and indices accessed without having to parenthesize the entire call chain.

A sequence of operations that are devoid of the object they will operate on I call a `Base.ChainLink`, and are constructed with “headless” `--` call chain syntax. Of course, a new chainlink can be created by composing other chainlinks. Ultimately, a full chain requires the source object too.

Although there’s heavy overlap between the functionality of the pipe operator `|>` and call chain syntax `--`, I suspect the latter will find greater use, as the former is [notoriously inconvenient to type](https://discourse.julialang.org/t/shortcut-to-type/47012) (among other reasons).

# Examples

* * *

_Starting a Spark Session_

```julia

SparkSession.builder--appName(_, "Main")--master(_, "local")--getOrCreate

```

_Chaining_

```julia

# notice that `(_)` and `(it)` cause no harm

[1, 2, 3] -- begin 
    filter(isodd, _)
    map(_^2, _)
    sum(it)
    sqrt(_) 
end ==
    3.1622776601683795

```

_More_

```julia

"1 2, 3; hehe4"--eachmatch(r"(\d+)", _).--(first; parse(Int, _))--join(_, ", ") ==
    "1, 2, 3, 4"

```

_Replacing_ `Base.Fix1` _and_ `Base.Fix2`

```julia

Base.values(x::MyStruct{K1,<:Any,K2,<:Any}) where {K1,K2} = 
    (values(x.a)..., map(getfield(x.b, _), filter(_∉K1, K2))...)

```

_Function Composition_

```julia

chain = --(split(_, r",\s*"); .--(parse(Int, it)^2); join(_, ", "))
"1, 2, 3, 4"--chain ==
    "1, 4, 9, 16" 

```

_Fluent Interface_

```julia

Kitten()--(setname(_,"Salem"), setcolor(_,:black), save)

```

_Transducer Chainlink_

```julia

process_bags = --begin
    mapcatting(unbundle_pallet)
    filtering(is_nonfood)
    mapping(label_heavy)
end
process_bags--into(airplane, _, pallets)

```

_Splatting Multiple Arguments_

```julia

(_^2, [1,2,3])--mapreduce(_, +, _) ==
    14

```

_Rearranging before splatting_

```julia

(:a,:b)--(reverse; f(_,_))

```

_Fully-Applied “Partially Applied” Function_

```julia

run = println("Hello", "world!", &_)

run()

```

_Example from [Chain.jl Readme](https://github.com/jkrumbiegel/Chain.jl)_

```julia
df--begin
    dropmissing
    filter(:id => >(6), _)
    groupby(_, :group)
    combine(_, :age => sum)
end

```

_Example from [DataPipes.jl Readme](https://gitlab.com/aplavin/DataPipes.jl)_

```julia
"a=1 b=2 c=3"--begin
    split
    .--(split(_, "="); Symbol(it[1]) => parse(Int, it[2]))
    NamedTuple
end

```

# New Pushback & Responses

* * *

> _In your examples you still have quite a lot of `_` underscores. How is this better than xyz.jl chaining package?_

Yes there are a handful more characters here and there, but it adds legibility and obviousness. It also makes use of existing idioms, which I think is a good thing.

Recall an important constraint: to settle on generic syntax for the language proper, not for a domain-specific package.

When things are a proper part of the language, then people can start to justify making tooling and tab-autocompletes for them. Hence, I don’t anticipate the extra `_`'s will be a nuisance in the near future.

> _This is sugar. Sugar belongs in macros._

Why? Pretty much everything in a language beyond parentheses is sugar; even infix operators are sugar.

Sugar improves productivity, and it encourages and promotes preferred idioms. Partial application is a good idiom to prefer in a functional language. And method chaining is a preferred idiom, well, everywhere (including in spoken languages and in mathematics).

> _Partial application and function call chaining are two separate concepts, and should not be in the same proposal together._

This is probably correct. I don’t know exactly when or why the two concepts got conflated, but they did—at least for me. Maybe they are just concepts that are easily conflatable?

> _Why do you mention autocomplete? Your proposal doesn’t solve any of the problems of autocomplete._

[I disagree. There are technical challenges, but I think the key issue is a matter of time and motivation. And motivation is helped by sugar that encourages the use of syntax that an autocomplete can work smoothly with.](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654/198)

> _The IDE doesn’t know what type of variable you’re operating with, so it’s impossible to find what methods will operate on it._

This would be a reasonable statement, if we decreed that nobody could run Julia in interactive mode.

[But that isn’t the case. Julia’s meant to be an interactive-mode language that runs fast: a scripting language that compiles. We already have property `.` autocomplete, but we don’t have method autocomplete. That should change.](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654/121)

And someday, somebody will implement an autocomplete that works outside interactive mode too. But we should crawl before we walk.

> _Can you give an example of how you imagine autocomplete might work?_

[Of course! Here’s a walked-through step-by-step example.](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654/182)

> _Can you provide sample code of how this autocomplete might work?_

[Yup! I made some sample code here.](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654/191)

[And here is how to use it.](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654/187)

> _Autocomplete is rendered useless by Julia’s method genericism; showing a method list sorted by type specialization is a bad idea, because so many methods are generic to `Any` type._

[I disagree, but I think this too is a solvable problem.](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654/191)

[Some more thoughts on how to do it.](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue/89654/193)

> _It’s impossible to reach consensus on such a contentious topic; we will never implement it into the language._

This is a strangely defeatist attitude for a language whose inspiration is to “have it all.” 😅

In my estimation, we need to settle on a syntax that’s generic enough, simple enough, and powerful enough, and remind ourselves that a good language feature will do something simple and natural, and do it very well, so that it can compose well with other language features. I’m hoping this proposal satisfied those objectives.

# Demo

* * *

Of course, this wouldn’t be complete without a demo!

> **This is a hacky demo, but it should do the trick.**
>
> **NOTE: I cannot update to my latest code, because I hit the character limit. See comment #32 for latest code and demos.**
> 
> Note: The `Fix` functors are pretty solid, but `macro demo_str` is hacky like hacky sack.
> 
> - It currently doesn’t work with broadcasting chainlinks (i.e., headless `.--`)
> - Broadcasting on functions _sometimes_ works and _sometimes_ doesn’t
> - The operator precedence of `--` in this demo is similar to multiplication (12), but it’s intended that it should have precedence similar to the `.` dot operator (17).
> 
> Code:
> 
> ```julia
> 
> ***DELETED; SEE COMMENT 32***
> 
> ```

> **Fun examples to try**
>
> **NOTE: I cannot update to my latest code, because I hit the character limit. See comment #32 for latest code and demos.**
> 
> ```julia
> 
> ***DELETED; SEE COMMENT 32***
> 
> ```

Play with it, compare with the dozen or so chaining packages out there, and let me know your thoughts! 💭

---

<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 17, 2022, 3:40pm UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/2 "2022-11-17T15:40:59Z")

</div>

`Fix` partial applicator simple runtime benchmark (optimized for 2-args):

```julia

julia> using FixArgs: Fix1 as Fix1a # for comparison w/ FixArgs.jl

julia> Fix1b = (f,x)->y->f(x,y); # for comparison w/ lambda

julia> f=(x,y)->x+y; f(1,2); g=Fix1(f, 1); @btime $g(2);
  1.800 ns (0 allocations: 0 bytes)

julia> f=(x,y)->x+y; f(1,2); g=Base.Fix1(f, 1); @btime $g(2);
  1.800 ns (0 allocations: 0 bytes)

julia> f=(x,y)->x+y; f(1,2); g=Fix1a(f, 1); @btime $g(2);
  1.800 ns (0 allocations: 0 bytes)

julia> f=(x,y)->x+y; f(1,2); g=Fix1b(f, 1); @btime $g(2);
  1.800 ns (0 allocations: 0 bytes)

julia> f=(x,y)->x+y; f(1,2); g=y->f(1, y); @btime $g(2); # type-unstable
  19.157 ns (0 allocations: 0 bytes)

```

`Fix` partial applicator simple compile time benchmark (optimized for 2-args):

```julia

julia> f=(x,y)->x+y; f(1,2); @time g=Fix1(f, 1); @time g(2);
  0.002998 seconds (1.98 k allocations: 135.662 KiB, 92.55% compilation time)
  0.002270 seconds (3.18 k allocations: 199.320 KiB, 97.75% compilation time)

julia> f=(x,y)->x+y; f(1,2); @time g=Base.Fix1(f, 1); @time g(2);
  0.002760 seconds (1.32 k allocations: 79.189 KiB, 97.32% compilation time)
  0.002505 seconds (2.39 k allocations: 146.041 KiB, 98.93% compilation time)

julia> f=(x,y)->x+y; f(1,2); @time g=Fix1a(f, 1); @time g(2);
  0.006897 seconds (9.29 k allocations: 557.034 KiB, 96.58% compilation time)
  0.004007 seconds (16.25 k allocations: 928.998 KiB, 99.33% compilation time)

julia> f=(x,y)->x+y; f(1,2); @time g=Fix1b(f, 1); @time g(2);
  0.003930 seconds (657 allocations: 38.885 KiB, 99.45% compilation time)
  0.003387 seconds (801 allocations: 46.613 KiB, 99.15% compilation time)

julia> f=(x,y)->x+y; f(1,2); @time g=y->f(1, y); @time g(2);
  0.000034 seconds (25 allocations: 1.609 KiB)
  0.002801 seconds (459 allocations: 29.776 KiB, 99.20% compilation time)

```

---

<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 18, 2022, 6:24am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/4 "2022-11-18T06:24:46Z")

</div>

Comparing this proposal to the syntax from the better-known chaining and piping packages:

* * *

## _Example from [Chain.jl](https://github.com/jkrumbiegel/Chain.jl) Readme:_

| Proposed CCS+PAS | Base Julia |
| --- | --- |
| 

```julia
df--begin
  dropmissing
  filter(:id => >(6), _)
  groupby(_, :group)
  combine(_, :age => sum)
end

```

 | 

```julia
df |>
  dropmissing |>
  x -> filter(:id => >(6), x) |>
  x -> groupby(x, :group) |>
  x -> combine(x, :age => sum)

```

 |
| [Chain.jl](https://github.com/jkrumbiegel/Chain.jl) | [DataPipes.jl](https://gitlab.com/aplavin/DataPipes.jl) |
| --- | --- |
| 

```julia
@chain df begin
  dropmissing
  filter(:id => >(6), _)
  groupby(:group)
  combine(:age => sum)
end

```

 | 

```julia
@p begin
  df
  dropmissing
  filter(:id => >(6), x)
  groupby(_, :group)
  combine(_, :age => sum)
end

```

 |
| [Pipe.jl](https://github.com/oxinabox/Pipe.jl) | [Lazy.jl](https://github.com/MikeInnes/Lazy.jl) |
| --- | --- |
| 

```julia
@pipe df |>
  dropmissing |>
  filter(:id => >(6), _)|>
  groupby(_, :group) |>
  combine(_, :age => sum)

```

 | 

```julia
@> df begin
  dropmissing
  x -> filter(:id => >(6), x)
  groupby(:group)
  combine(:age => sum)
end

```

 |
| [Underscores.jl](https://c42f.github.io/Underscores.jl/stable/) | [Hose.jl](https://github.com/FNj/Hose.jl) |
| --- | --- |
| 

```julia
@_ df |>
  dropmissing |>
  filter(:id => >(6), __) |>
  groupby(__, :group) |>
  combine(__, :age => sum)

```

 | 

```julia
@hose df |>
  dropmissing |>
  filter(:id => >(6), _) |>
  groupby(_, :group) |>
  combine(_, :age => sum)

```

 |

* * *

## _Example from [DataPipes.jl](https://gitlab.com/aplavin/DataPipes.jl) Readme_

| Proposed CCS+PAS | Base Julia |
| --- | --- |
| 

```julia
"a=1 b=2 c=3"--begin
  split
  map(_) do --
    split(_, "=")
    Symbol(it[1]) => parse(Int, it[2])
  end
  NamedTuple
end

```

 | 

```julia
"a=1 b=2 c=3" |>
  split |>
  it->map(it) do it
    it=split(it, "=")
    Symbol(it[1]) => parse(Int, it[2])
  end |>
  NamedTuple

```

 |
| [Chain.jl](https://github.com/jkrumbiegel/Chain.jl) | [DataPipes.jl](https://gitlab.com/aplavin/DataPipes.jl) |
| --- | --- |
| 

```julia
@chain "a=1 b=2 c=3" begin
  split
  map(_) do it
    it=split(it, "=")
    Symbol(it[1]) => parse(Int, it[2])
  end
  NamedTuple
end

```

 | 

```julia
@p let
  "a=1 b=2 c=3"
  split
  map() do __  
    split(__, '=')
    Symbol(__[1]) => parse(Int,__[2])
  end
  NamedTuple
end

```

 |
| [Pipe.jl](https://github.com/oxinabox/Pipe.jl) | [Lazy.jl](https://github.com/MikeInnes/Lazy.jl) |
| --- | --- |
| 

```julia
@pipe "a=1 b=2 c=3" |>
  split |>
  map(_) do it
    it=split(it, "=")
    Symbol(it[1]) => parse(Int, it[2])
  end |>
  NamedTuple

```

 | 

```julia
@>> "a=1 b=2 c=3" begin
  split
  map(it->begin
    it=split(it, "=")
    Symbol(it[1]) => parse(Int, it[2])
  end)
  NamedTuple
end

```

 |
| [Underscores.jl](https://c42f.github.io/Underscores.jl/stable/) | [Hose.jl](https://github.com/FNj/Hose.jl) |
| --- | --- |
| 

```julia
@_ "a=1 b=2 c=3" |>
  split |>
  map(it->begin
    it=split(it, "=")
    Symbol(it[1]) => parse(Int, it[2])
  end, __) |>
  NamedTuple

```

 | 

```julia
@hose "a=1 b=2 c=3" |>
  split |>
  map(_) do it
    it=split(it, "=")
    Symbol(it[1]) => parse(Int, it[2])
  end |>
  NamedTuple

```

 |

* * *

## _Examples from [Pipe.jl](https://github.com/oxinabox/Pipe.jl) Readme_

| Proposed CCS+PAS | Base Julia |
| --- | --- |
| 

```julia
a--b(_...)
a--b(it(1,2))
a--b(it[3])
(2,4)--get_angle(_,_)

```

 | 

```julia
a |> x->b(x...)
a |> x->b(x(1,2))
a |> x->b(x[3])
(2,4) |> x->get_angle(x[1],x[2])

```

 |
| [Chain.jl](https://github.com/jkrumbiegel/Chain.jl) | [DataPipes.jl](https://gitlab.com/aplavin/DataPipes.jl) |
| --- | --- |
| 

```julia
@chain a b(_...)
@chain a b(_(1, 2))
@chain a b(_[3])
@chain (2,4) get_angle(_[1],_[2])

```

 | 

```julia
@p a b(__...)
@p a b(__(1,2))
@p a b(__[3])
@p (2,4) get_angle(__[1],__[2])

```

 |
| [Pipe.jl](https://github.com/oxinabox/Pipe.jl) | [Lazy.jl](https://github.com/MikeInnes/Lazy.jl) |
| --- | --- |
| 

```julia
@pipe a |> b(_...)
@pipe a |> b(_(1, 2))
@pipe a |> b(_[3])
@pipe (2,4) |> get_angle(_[1],_[2])

```

 | 

```julia
# N/A
# N/A
# N/A
# N/A

```

 |
| [Underscores.jl](https://c42f.github.io/Underscores.jl/stable/) | [Hose.jl](https://github.com/FNj/Hose.jl) |
| --- | --- |
| 

```julia
@_ a |> b(__...)
@_ a |> b(__(1,2))
@_ a |> b(__[3])
@_ (2,4) |> get_angle(__[1],__[2])

```

 | 

```julia
@hose a |> b(_...)
@hose a |> b(_(1,2))
@hose a |> b(_[3])
@hose (2,4) |> get_angle(_[1],_[2])

```

 |

* * *

## _My Examples_

| Proposed CCS+PAS | Base Julia |
| --- | --- |
| 

```julia
[1,2,3]--map(_^2, _)
[1,2,3]--join(_, ", ")
"1"--parse(Int, _) == 1
(:a,:b)--reverse--f(_...)

```

 | 

```julia
[1,2,3] |> x->map(x->x^2, x)
[1,2,3] |> x->join(x, ", ")
([1,2,3] |> x->parse(Int, x)) == 1
(:a,:b) |> reverse |> x->f(x...)

```

 |
| [Chain.jl](https://github.com/jkrumbiegel/Chain.jl) | [DataPipes.jl](https://gitlab.com/aplavin/DataPipes.jl) |
| --- | --- |
| 

```julia
@chain [1,2,3] map(x->x^2, _)
@chain [1,2,3] join(", ")
@chain("1", parse(Int, _)) == 1
@chain (:a,:b) reverse f(_...)

```

 | 

```julia
@p [1,2,3] map(_^2)
@p [1,2,3] join(__, ", ")
@p("1", parse(Int)) == 1
@p (:a,:b) reverse f(__...)

```

 |
| [Pipe.jl](https://github.com/oxinabox/Pipe.jl) | [Lazy.jl](https://github.com/MikeInnes/Lazy.jl) |
| --- | --- |
| 

```julia
@pipe [1,2,3] |> map(x->x^2, _)
@pipe [1,2,3] |> join(_, ", ")
@pipe("1" |> parse(Int, _)) == 1
@pipe (:a,:b) |> reverse |> f(_...)

```

 | 

```julia
@>> [1,2,3] map(x->x^2)
@> [1,2,3] join(", ")
@>>("1", parse(Int)) == 1
# N/A

```

 |
| [Underscores.jl](https://c42f.github.io/Underscores.jl/stable/) | [Hose.jl](https://github.com/FNj/Hose.jl) |
| --- | --- |
| 

```julia
@_ [1,2,3] |> map(_^2, __)
@_ [1,2,3] |> join(__, ", ")
@_("1" |> parse(Int, __)) == 1
@_ (:a,:b) |> reverse |> f(__...)

```

 | 

```julia
@hose [1,2,3] |> map(x->x^2, _)
@hose [1,2,3] |> join(_, ", ")
@hose("1" |> parse(Int, _)) == 1
@hose (:a,:b) |> reverse |> f(_...)

```

 |

* * *

## _Of interest:_

Many of these packages implement single-underscore `_` and double-underscore `__`, each with a meaning, if not equal to, approximating:

1. Placeholder to specify argument position for partial function evaluation (“tight currying”)
2. Return value of last element in execution chain (“loose binding”)

It’s fairly confusing to identify which is which, because they _seem_ to have similar meanings in these contexts, they look almost identical, and different packages swap their symbols or give them slightly different meanings.

In this proposal, to avoid confusing the two, I call #2 `it` reflecting the analogous role of the pronoun in the English language for method chaining, and I leave #1 as the most basic Scala-style argument placeholder `_` for partial function evaluation.

My proposal intends to keep the behavior rules as simple and consistent as possible, to create a syntax that composes well, instead of fragile and complicated rules.

* * *

## _Some color on the word “it”_

Method chaining is a common idiom in natural language too. In some instances, a sequence of methods is directly composable. For example (in pseudo-English):

> Cat: Put on lap. Inspect fur. Find flea. Pull off. Put in soapy water.

In these instances, the pronoun “it” can be implied. In other instances, when methods are not directly composable and minor glue logic is necessary to ready an object for the next step in the chain, we must make “it” explicit:

> Baby: Pick up. Lift its head above its legs. Put butt on your arm. Rock to sleep.

Notice that without the glue employing “it,” the composition might not work very well.

The call chain syntax I propose here intends to handle these cases, and uses the same keyword “it” for the exact same reasons.

Of course, there are more sophisticated scenarios where each object must be specified at each step:

> Pot, oats, milk: Put the pot on the stove. Put oats in the pot. Pour milk in the pot. Turn on the stove.

For these more general (and more verbose) scenarios, we already have lambdas and named functions.

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [November 18, 2022, 6:31am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/5 "2022-11-18T06:31:28Z")

</div>

Chain.jl makes the `_,` unnecessary in `@chain d groupby(_, c)` but it is still necessary under `--`. Why is `--` better?

---

<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 18, 2022, 6:41am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/6 "2022-11-18T06:41:49Z")

</div>

> [@jar1](#):
>
> Chain.jl makes the `_,` unnecessary in `@chain d groupby(_, c)` but it is still necessary under `--`. Why is `--` better?

See:

> [@uniment](#):
>
> > _In your examples you still have quite a lot of `_` underscores. How is this better than xyz.jl chaining package?_
> 
> Yes there are a handful more characters here and there, but it adds legibility and obviousness. It also makes use of existing idioms, which I think is a good thing.
> 
> Recall an important constraint: to settle on generic syntax for the language proper, not for a domain-specific package.
> 
> When things are a proper part of the language, then people can start to justify making tooling and tab-autocompletes for them. Hence, I don’t anticipate the extra `_`'s will be a nuisance in the near future.

It’s possible to specify `--` to operate more like `@chain`, and to default to inserting the argument in the front of the argument list. However, this creates inconsistent behavior, increasing mental load and making the operator’s behavior more fragile.

For a language feature, you want behavior to be simple and consistent, so that it will compose well. Sometimes this will be annoying because it’s a little bit more verbose, but sometimes it can really save your bacon.

For example: What if one day, you decide that you want a part of your chain to be a _return value from another function call_? Using `--`, you would do something like this:

```julia
x--(f, my_function_generator(arg1, arg2), g)

```

With `@chain`’s behavior to automatically insert `it` into first argument position, this becomes treacherous; you basically have to abandon all of the investment you put into learning its idioms, simply because it did not anticipate your new use case.

So it’s better to have more simple, generic behavior, which partial application syntax is. Tab-autocomplete will make the experience even smoother, once we have it.

---

<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 18, 2022, 6:49am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/7 "2022-11-18T06:49:49Z")

</div>

* * *

## _A Fun (and productive) Experiment_

A repeated complaint about “tight-currying” is that it binds too tightly for a lot of the common expressions we might wish for. For example, how do we express 1 + x - 2?

```julia
julia> demo" 1 + _ - 2 "
ERROR: MethodError: no method matching -(::Fix1{typeof(+), Int64}, ::Int64)

```

Oh no! An error! That’s because “tight-currying” was greedy, and now we’re trying to subtract an integer from a function instead of just having a lambda that represents the entire expression.

But remember the point of simple syntax with consistent rules, is that it’s composable with other syntax and operators. So maybe we can solve the problem with… wait for it… _composition!_ _(ba-dum-tsss)_

```julia
julia> Base.:-(x::Fix, y) = Fix2(-, y) ∘ x

julia> demo" 1 + _ - 2 "
-(_, 2) ∘ +(1, _)

julia> demo" 1 + _ - 2 "(3)
2

```

Yay!

Here’s another example: \sin(\cos(x)):

```julia
julia> Base.sin(x::Fix) = sin ∘ x

julia> demo" sin(cos(_)) "
sin ∘ cos(_)

julia> demo" sin(cos(_)) "(1)
0.5143952585235492

```

Composing tight currying with function composition. A thing of beauty.

Now, because _any object_ can be a function, maybe this behavior shouldn’t be constrained to just `Fix` objects…

![BatmanThinkingGIF](https://global.discourse-cdn.com/julialang/original/3X/2/d/2d085f0197ab09f668b53283eed9ae4b1dbdd5dc.gif)

If nothing else though, the type `Fix` makes it explicit that it’s a _partial function_ rather than any arbitrary function or object, and therefore this behavior could be intended, so it seems safe enough to explore.

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [November 18, 2022, 6:54am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/8 "2022-11-18T06:54:46Z")

</div>

> [@uniment](#):
>
> What if one day, you decide that you want a part of your chain to be a _return value from another function call_?

> With `@chain`’s behavior to automatically insert `it` into first argument position, this becomes treacherous; you basically have to abandon all of the investment you put into learning its idioms, simply because it did not anticipate your new use case.

It’s just

```julia
@chain x f my_function_generator(arg1, arg2)(_) g

```

---

<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 18, 2022, 7:03am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/9 "2022-11-18T07:03:51Z")

</div>

Hah! I didn’t think of that one 😜 You might consider removing the underscore there too, for legibility.

---

<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 18, 2022, 10:14am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/10 "2022-11-18T10:14:04Z")

</div>

> [@jar1](#):
>
> Why is `--` better?

To answer this question more directly:

I could have specified that `--` would behave as `@chain` does. However, for a generic language feature, the inconsistency in behavior has a bad feel to it, and when autocomplete is eventually available it will feel short-sighted. I did not feel this way toward my previous proposal, because although it showed a clear preference to the first argument too, it was very simple and _always behaved that way_. I have a strong preference for _consistency,_ _transparency,_ and _obviousness_ in behavior of language features.

I think the real benefits of `--`, however, lie in its other properties. Namely,

1. By (aspiring to be) a language feature, people could unify their efforts to create tooling (e.g., tab autocomplete). This is difficult when you have a patchwork of different macros.
2. By being an infix operator, you don’t have to skip back when you remember you want to call a function on the object; you just do it. This reduces mental load.
3. By having high precedence (similar to `.` dot property access), you don’t have to parenthesize the entire expression when you wish to get a property or an index of the result. The benefits of this are difficult to appreciate from the examples, because they never show _what happens next after the chain_ 😅 But in short, it makes it much more convenient when the chain is an inline expression. The intent is for you to prefer `--` over regular function call syntax, which you will when autocomplete eventually comes online.
4. `--` chains and chainlinks can be nested.
5. Chainlinks (created from “headless” `--`) can be saved and reused, and chains can be broadcasted with `.--`.
6. The use of `it` means that `_` can be reserved for partial function application, which I believe is a more suitable role.
7. ~~The insistence on a `:tuple` of expressions means that links in the chain are separated by commas, which is more consistent with them being _functions_ or _expressions_ but not full _statements_. With `@chain`, it’s a bit less clear what the answer to the question is, of “What is the object I am now dealing with?”~~ nevermind lol
8. Just a restatement of #1. It should be a language feature, not a macro.

Hope that covers it 😉

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [November 18, 2022, 10:45am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/11 "2022-11-18T10:45:45Z")

</div>

I guess I can buy the story about not wanting to prefer the first argument. That said, it’s gonna be the first or the last in 95 percent of cases. Clojure has `->` and `->>` for left and right chain. Julia could have `@\>` and `@/>`.

> [@uniment](#):
>
> The insistence on a `:tuple` of expressions means that links in the chain are separated by commas, which is more consistent with them being _functions_ or _expressions_ but not full _statements_.

My initial instinct is that the commas are just annoying and don’t add much value. We’re in a special syntax context after all, might as well use it.

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [November 18, 2022, 12:26pm UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/12 "2022-11-18T12:26:18Z")

</div>

I made Chain.jl with a specific focus on DataFrame manipulation, that’s why it defaults to first argument insertion. It’s not necessarily the theoretically best choice, it just seemed the most practical to me at the time.

---

<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 18, 2022, 6:47pm UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/13 "2022-11-18T18:47:12Z")

</div>

> [@jules](#):
>
> I made Chain.jl with a specific focus on DataFrame manipulation, that’s why it defaults to first argument insertion. It’s not necessarily the theoretically best choice, it just seemed the most practical to me at the time.

As they say, there’s nothing more permanent than a temporary solution 😝

Meanwhile, DataPipes.jl has made the analogous decision for `FlexiGroup` manipulation to insert into last position instead. 😅

> [@jar1](#):
>
> That said, it’s gonna be the first or the last in 95 percent of cases. Clojure has `->` and `->>` for left and right chain. Julia could have `@\>` and `@/>`.

I agree, and Lazy.jl implements `@>` and `@>>` which mimic these Clojure macros.

The problem arises: what do you do when it changes mid-way through a chain? For example:

```julia
"HEAD a1 a2 a3"--(replace(_, r"HEAD\s*"=>""), match(r".*(\d+).*(\d+).*(\d+).*", _)).--(first, parse(Int,_), _^2)--join(_, ", ")

```

> **To see what this does**
>
> run demo code:
> 
> ```julia
> demo""" "HEAD a1 a2 a3"--(replace(_, r"HEAD\s*"=>""), match(r".*(\d+).*(\d+).*(\d+).*", _)).--(first, parse(Int,_), _^2)--join(_, ", ") """
> 
> ```

Notice that `match` and `replace` are both string search operators, and `parse` and `join` both “convert” strings, yet receive the string in different argument positions 😅 This chain will be _very_ inconvenient to attempt using Clojure’s thread-first and thread-last macros.

To solve this, two weeks ago I proposed two _infix_ operators fix-first and fix-last, which allow you to flip mid-chain. That solves this problem and is natural enough, because they _always_ replace the specified argument.

However, underscore PAS is capable of fixing _any argument_ in _any position_. This comes with the benefit of not having to pre-determine which argument you will fix! The downside is, you’re not pre-determining which argument you will fix. The magical behavior of “choose any argument position! but if you don’t specify, I’m just gonna sneak it into this position for you” is a bit too sneaky for a generic language feature.

That said, the nice thing about generic language features is that _people can justify the effort to build tooling for them_. That means an autocomplete will be able to fill in the underscore for you (using type specialization and/or heuristics and/or statistical inference to determine which position you’re most likely to chain into).

---

<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:** [November 18, 2022, 6:56pm UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/14 "2022-11-18T18:56:12Z")

</div>

> [@uniment](#):
>
> Meanwhile, DataPipes.jl has made the analogous decision for `FlexiGroup` manipulation to insert into last position instead. 😅

`DataPipes` predate `FlexiGroups` by a lot (:  
`DataPipes` puts the “previous” argument last because it’s typical for generic data manipulation functions both in Base Julia and across the ecosystem. The first argument(s) are often “lambda” functions, and the piped argument comes after them.  
`FlexiGroups` just uses the same (official) convention, and is one of many packages doing so. It’s not like it is the first `group(key, X)` function out there (:

More on topic, your examples don’t really work for me. For example, the second “Base Julia” example errors with `it not defined`.  
And also those example don’t look _that_ much cleaner with this proposal compared to base julia. I think the bar for introducing new syntax into the language should be higher…

---

<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 18, 2022, 7:04pm UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/15 "2022-11-18T19:04:03Z")

</div>

> [@aplavin](#):
>
> `DataPipes` predate `FlexiGroups` by a lot (:  
> `DataPipes` puts the “previous” argument last because it’s typical for generic data manipulation functions both in Base Julia and across the ecosystem. The first argument(s) are often “lambda” functions, and the piped argument comes after them.  
> `FlexiGroups` just uses the same (official) convention, and is one of many packages doing so. It’s not like it is the first `group(key, X)` function out there (:

Thanks for the history lesson 🙏

> [@aplavin](#):
>
> For example, the second “Base Julia” example errors with `it not defined`.

Oops! Fixed.

> [@aplavin](#):
>
> And also those example don’t look _that_ much cleaner with this proposal compared to base julia. I think the bar for introducing new syntax into the language should be higher…

It’s not just about _looking_ cleaner, sir.

---

<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 18, 2022, 9:09pm UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/16 "2022-11-18T21:09:34Z")

</div>

> [@jar1](#):
>
> My initial instinct is that the commas are just annoying and don’t add much value. We’re in a special syntax context after all, might as well use it.

My instinct is that even in special syntax contexts, we want to keep things feeling as “normal” as possible to minimize the shocking “oh, I’ve just jumped into a new language!” sensation. The commas are also aligned with their use in natural language, of delimiting clauses, and are easy to type anyway.

But now that I dwell on it, I’m growing more partial to the idea of still requiring them for inline expressions (i.e., `:tuple`), but not requiring them for block expressions (i.e., `:block`). We already have analogous behavior for `Vector`s.

I will spend some time digesting this.

---

<div class="post-metadata">

**Author:** ![bertschi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bertschi/32/33462_2.png) [@bertschi](https://discourse.julialang.org/u/bertschi)\
**Post date:** [November 19, 2022, 12:42am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/17 "2022-11-19T00:42:07Z")

</div>

Must admit that I did not follow all details of the new proposal. Yet, it is great to see the options from different packages collected in one place.  
To me, the base Julia version do not seem much worse, i.e., Julia already has a lot of concise syntax. Furthermore, I just realized that it allows _hanging lambdas_ a la Haskell:

```julia
df |>
  dropmissing |> x ->
  filter(:id => >(6), x) |> x ->
  groupby(x, :group) |> x ->
  combine(x, :age => sum)

"a=1 b=2 c=3" |>
  split |> it->
  map(it) do it
    split(it, "=") |> it ->
    Symbol(it[1]) => parse(Int, it[2])
  end |>
  NamedTuple

```

Thereby, the inserted argument can be moved a bit out of side, i.e., together with an editor shortcut for inserting `|> it ->` or `|> ⬚ ->` and maybe jumping to the next line this might be a workable solution already?

At least for simple chains, some higher order functions might also be quite nice:

```julia
∝(i, f) = (args...) -> it -> f(args[1:i-1]..., it, args[i:end]...) # inserts it at the i-th position
⊚(f, g) = g ∘ f # reverse composition

[1,2,3] |> (2∝filter)(isodd) ⊚ (2∝map)(sqrt)
df |>
  dropmissing ⊚
  (2∝filter)(:id => >(6)) ⊚
  (1∝groupby)(:group) ⊚
  (1∝combine)(:age => sum)

```

Kind of tacit programming with numbered arguments …

---

<div class="post-metadata">

**Author:** ![TheCedarPrince](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thecedarprince/32/17323_2.png) [@TheCedarPrince](https://discourse.julialang.org/u/TheCedarPrince)\
**Post date:** [November 19, 2022, 12:56am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/18 "2022-11-19T00:56:30Z")

</div>

@uniment , I have been following your proposals with great interest over the past couple weeks. From a practical perspective in the area of my work, would you be willing to comment on my workflow with my tools? I really enjoyed seeing your comparisons to other tools but am struggling a bit to see how to apply your syntax in my workflow. Thanks!

---

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [November 19, 2022, 1:29am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/19 "2022-11-19T01:29:46Z")

</div>

> [@bertschi](#):
>
> Furthermore, I just realized that it allows _hanging lambdas_ a la Haskell:
> 
> ```julia
> df |>
> dropmissing |> x ->
> filter(:id => >(6), x) |> x ->
> groupby(x, :group) |> x ->
> combine(x, :age => sum)
> 
> ```

Wow, that’s neat. I did not know that.

---

<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 19, 2022, 1:57am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/20 "2022-11-19T01:57:19Z")

</div>

> [@bertschi](#):
>
> Julia already has a lot of concise syntax

I agree! I love it.

> [@bertschi](#):
>
> the base Julia version do not seem much worse

I disagree. But this depends on how frequently (and where) the feature is going to be used.

For multi-line chains, which will be fairly sparse in a given codebase, using base Julia for a call chain is ugly but not too bad. But for short, inline chains, the extra characters add enough effort and visual noise as to be prohibitive. For example:

```julia
my_func(x, my_arr--meth(_,1), z)
# versus
my_func(x, my_arr |> x->meth(x,1), y)

```

Or, let’s say you want to access a property _after the chain_:

```julia
x--foo--bar(_,y).a[1]
# versus
(x |> foo |> x->bar(x,y)).a[1]

```

With the current syntax, you have to go back to the beginning of the pipe chain to parenthesize it, which means you won’t really want to use pipes at all. It’s very discouraging.

Moreover, every time you define a lambda, it’s compiled from scratch (this includes defining a `struct` to hold the fixed arguments). This takes typically about ten milliseconds. It’s pretty wasteful to have a lot of these in your codebase, for functions that will only ever be used once.

Unless a compiler optimization is added for it, but that would be pretty sad just to defend a language feature so bad that it was on the verge of deprecation, but only survived because there wasn’t a better alternative (yes of course, `|>`).

So overall, for an idiom as common as call chaining, using lambdas here is so wasteful both visually and computationally that you won’t want to do it.

> [@bertschi](#):
>
> At least for simple chains, some higher order functions might also be quite nice:

This is an interesting take on partial function application 😉 Using the `Fix` definition in the demo code,

```julia
function ∝(i, f)
    (a...;k...) -> Fix{((1:i-1)..., (i+1:length(a)+1)...), length(a)+1}(f, a[1:i-1]..., a[i:end]...; k...)
end

```

Underscore PAS is just too perfect for defining partial functions though…

---

<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 19, 2022, 2:17am UTC](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408/21 "2022-11-19T02:17:41Z")

</div>

I’m probably the worst person to ask for advice on workflow tooling 😅 but sure! drop me a DM

[Next page](https://discourse.julialang.org/t/fixing-the-piping-chaining-partial-application-issue-rev-2/90408.md?page=2)
