How about a syntax `f.↑(1:I, 1:J)`

so that you can do, e.g.,

```
julia> tuple.↑([1,2,3],[4,5])
3×2 Array{Tuple{Int64,Int64},2}:
(1, 4) (1, 5)
(2, 4) (2, 5)
(3, 4) (3, 5)
```

Here is an implementation based on @rdeits’s `outer`

:

```
@generated function outer_broadcasted(f, args::Tuple{Vararg{Any, N}}) where {N}
expr = Expr(:call, Broadcast.broadcasted, :f) # instead of broadcast
for i in 1:N
if i == 1
push!(expr.args, :(args[1]))
else
push!(expr.args, Expr(:call, :reshape, :(args[$i]), Expr(:tuple, [1 for _ in 1:i - 1]..., :(:))))
end
end
expr
end
↑(f, args::Tuple) = Broadcast.materialize(f.↑(args))
Broadcast.broadcasted(::typeof(↑), f, args) = outer_broadcasted(f, args)
```

It’ll be fused with other dotted functions:

```
julia> Meta.@lower f.↑(x, y) .+ z
:($(Expr(:thunk, CodeInfo(
1 ─ %1 = (Core.tuple)(x, y)
│ %2 = (Base.broadcasted)(↑, f, %1)
│ %3 = (Base.broadcasted)(+, %2, z)
│ %4 = (Base.materialize)(%3)
└── return %4
))))
```

A downside is that `f.↑(x, y) .^ z`

does not work as you’d expect.