Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Indexing is function evaluation: f[A,B,C] for orthogonal broadcasts #40295

Closed
wants to merge 1 commit into from

Conversation

mbauman
Copy link
Member

@mbauman mbauman commented Apr 1, 2021

f[A, B, Cs...] can now be used to evaluate the function f over the cartesian product of the arguments A, B, Cs... akin
to how indexing behaves.

Long have we wanted the ability to broadcast over orthogonal (Cartesian) products of the arguments, and while we've thought about making indexing behave more like broadcasting, it somehow never occurred to us that we should go the other way! This PR implements f[A,B,C] to evaluate f in the manner that indexing would index (with APL-style dim-sum Cartesian products)!

For example, we can create the times table quite simply:

julia> (*)[1:4, 1:10]
4×10 Matrix{Int64}:
 1  2   3   4   5   6   7   8   9  10
 2  4   6   8  10  12  14  16  18  20
 3  6   9  12  15  18  21  24  27  30
 4  8  12  16  20  24  28  32  36  40

But of course we're not limited to arrays of integers or even numbers:

julia> occursin[["a","b"],["foo","bar","baz","boo"]]
2×4 BitMatrix:
 0  1  1  0
 0  1  1  1

In this case, broadcasting would be especially awkward since we can't use ["foo"]'.

Indexing is the one real API. Let's use it everywhere.

@mbauman mbauman added triage This should be discussed on a triage call priority This should be addressed urgently labels Apr 1, 2021
@simonbyrne
Copy link
Contributor

Indexing is the one real API. Let's use it everywhere.

👍: we should also switch to () for indexing as well.

## [Function calls with orthogonal indexing](@id man-orthogonal-functions)

Just as indexing into an array computes the orthogonal (cartesian) product of the indices to
evaluate for [non scalar indexing](@ref man-indexing), the indexing syntax `f[A, B, C...]` may
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
evaluate for [non scalar indexing](@ref man-indexing), the indexing syntax `f[A, B, C...]` may
evaluate for [non scalar indexing](@ref man-array-indexing), the indexing syntax `f[A, B, C...]` may

@mschauer
Copy link
Contributor

mschauer commented Apr 1, 2021

Hey, guys/gals, don't make fun of me... https://gist.github.com/mschauer/b04e000e9d0963e40058 !?

@tkf
Copy link
Member

tkf commented Apr 1, 2021

julia/base/broadcast.jl

Lines 19 to 20 in 8a931b6

### Orthoganol broadcast f[X,Y,Z]
getindex(f::Function, args...) = broadcast(Base.splat(f), Iterators.product(args...))

It is truly amazing that such a simple definition introduces a very useful programming tool!

...But, I think it's better to avoid dispatching on the Function type. There was a similar discussion when creating mergewith API: #34296

In Julia, anything can potentially define a call overload and so anything can act like a function. With this patch, we can't simply explain that [f(a, b) for a in A, b in B] is always equivalent to f[A, B]. You have to know the type of f. It makes it hard to use duck typing that is one of the big composability facilitators in Julia.

I think a cleaner solution is to add a new (postfix) syntax, just like the usual broadcasting works. This way, we can reason about how the program works (more or less) syntactically.

@goretkin
Copy link
Contributor

goretkin commented Apr 1, 2021

I have the same concern as @tkf (and think Function is more or less always meaningless).

I also think this is weird:

julia> Base.getindex(f::Function, args...) = broadcast(Base.splat(f), Iterators.product(args...))

julia> string["hey"]
3-element Vector{String}:
 "h"
 "e"
 "y"

Perhaps any extra verbosity defeats the purpose, and it requires choosing a name, but I would prefer a design like

julia> struct Shim{F}
       f::F
       end

julia> Base.getindex(s::Shim, args...) = broadcast(Base.splat(s.f), Iterators.product(args...))

julia> Shim(cos)[0]
1.0

I think the last line looks so much less weird than

julia> cos[0]
1.0

I know we don't have formal interfaces in julia, but I also think this does not seem like getindex. Aside from the pun that allows Int[], I don't think there are any types that define getindex but not, say, keys or iterate.

@mbauman
Copy link
Member Author

mbauman commented Apr 1, 2021

Shoot, looks like a quaint US tradition doesn't translate well across cultures, my sincerest apologies to everyone who took this seriously. I am not serious here.

@mbauman mbauman closed this Apr 1, 2021
@mbauman mbauman removed priority This should be addressed urgently triage This should be discussed on a triage call labels Apr 1, 2021
@mbauman mbauman deleted the mb/one-rl-api branch April 1, 2021 21:40
@goretkin
Copy link
Contributor

goretkin commented Apr 1, 2021

I filled the date as "April Fools, 2021" on a form today and I have to say, you still got me. Thanks for the fun!

(but we really should get rid of Function as much as possible, imo)))))

@AriMKatz
Copy link

AriMKatz commented Apr 1, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants