Alex Craft
Alex Craft

Reputation: 15336

Function chaining in Julia

Is it possible to write multiple function calls as a chain?

sum(
  map(parseIgFloat, 
    map((row) -> row.PL_Amount, 
      filter((row) -> !ismissing(row.Summary) && row.Summary == "Cash In", 
        collect(ts)
      )
    )
  )
)

Turn it into something like:

ts 
  |> collect
  |> filter((row) -> !ismissing(row.Summary) && row.Summary == "Cash In")
  |> map((row) -> row.PL_Amount)
  |> map(parseIgFloat)
  |> sum

Upvotes: 3

Views: 1991

Answers (3)

DNF
DNF

Reputation: 12654

I would probably write it like this:

sum(row -> parseIgFloat(row.PL_Amount),
    filter((row) -> !ismissing(row.Summary) && row.Summary == "Cash In", 
            ts)
    )

If and when underscore currying comes about (https://github.com/JuliaLang/julia/pull/24990) you could simplify it like this:

sum(parseIgFloat(_.PL_Amount)), 
    filter(!ismissing(_.Summary) && _.Summary == "Cash In",  # actually not sure if this line would work
           ts)
    )

At that point chaining might also become more convenient.

By the way: don't use collect unless you somehow really have to.

Upvotes: 1

Tasos Papastylianou
Tasos Papastylianou

Reputation: 22215

In general, what you ask is difficult, since whatever construct you came up with, you would have to guarantee the order of arguments as passed is the order expected by the function, so there is no general enough method to allow you to do this without defining explicit types and operations for it.

However, with respect to map and filter specifically, it is trivial to create 'curried' versions of these functions and apply chaining to them. E.g.

# Create curried versions of map and filter for use in 'chaining'
import Base.map;    function    map(f); return L ->    map(f,L); end;
import Base.filter; function filter(f); return L -> filter(f,L); end;


f  = x -> x ^ 2;
ts = range(1, stop=10);

( ts
  |>  collect          
  |>  map(f)             # square the collection
  |>  filter(iseven)     # keep only even results
  |>  sum              
)

Output:

220


PS: Chaining is mostly about ease of readability, and is most useful when you have a succession of individually simple and visually straightforward commands, as above. If you're going to have complicated expressions in your 'chain', like the ones in your proposed solution, then it's not really worth it in my opinion. Either wrap your complicated expressions into appropriately named functions that make the chain read like plain english, or avoid chaining in the first place, and rely on clear steps using temporary variables instead.

PS2: Also note that, the |> operator is a valid target for broadcasting, like any function. Therefore |> map(f) above could also have been written more simply as .|> f instead.

Upvotes: 3

Alex Craft
Alex Craft

Reputation: 15336

After searching, this seems to be the best option available

ts |> 
  collect |> 
  (list -> filter((row) -> !ismissing(row.Summary) && row.Summary == "Cash In", list)) |>
  (list -> map((row) -> row.PL_Amount, list)) |>
  (list -> map(parseIgFloat, list)) |>
  sum

or with the Pipe package with the macros

@pipe ts |> 
  collect |> 
  filter((row) -> !ismissing(row.Summary) && row.Summary == "Cash In", _) |>
  map((row) -> row.PL_Amount, _) |>
  map(parseIgFloat, _) |>
  sum

Upvotes: 4

Related Questions