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

Loosen signature for repeat() #14082

Merged
merged 1 commit into from
May 24, 2016
Merged

Loosen signature for repeat() #14082

merged 1 commit into from
May 24, 2016

Conversation

nalimilan
Copy link
Member

Put 'outer' first as it is more natural to omit 'inner'. Deprecate the old version with keyword arguments.
Also change the signature to accept any AbstractArray as first argument, and any type for 'outer' and 'inner', to allow in particular passing tuples or arrays. Allow passing an empty collection to imply a no-op.

Fixes #12953.

I suspect I should update the docs after moving them out of helpdb.jl, but I don't know the command to run.

@tkelman
Copy link
Contributor

tkelman commented Nov 21, 2015

adjust the rst signature to be consistent with your change, then run ./julia doc/genstdlib.jl

Pass an empty tuple as `outer` (or any other empty collection, which is equivalent
to a tuple of ones of length `ndims(a)`) to use only inner repetitions.

Examples:
Copy link
Contributor

Choose a reason for hiding this comment

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

these should probably be doctests

Copy link
Member Author

Choose a reason for hiding this comment

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

Right.

@tkelman
Copy link
Contributor

tkelman commented Nov 21, 2015

I personally think keyword arguments are clearer than positional. Is this working around an inference limitation that will be fixed soonish?

@nalimilan
Copy link
Member Author

adjust the rst signature to be consistent with your change, then run ./julia doc/genstdlib.jl

Thanks, but I can't get it to work. Should the signature be the one in the code or in the doc? (Either way, it doesn't work...)

Regarding keyword vs. positional arguments, @simonster noted on the issue that inference would be possible, but I've not been able to get it to work for now.

@nalimilan nalimilan force-pushed the nl/repeat branch 2 times, most recently from d4147f7 to 29a44ad Compare November 22, 2015 21:40
@nalimilan
Copy link
Member Author

I managed to get the docs to work, I hadn't rebuilt Julia correctly before updating them. I've also added doctests.

@nalimilan
Copy link
Member Author

Can somebody comment about the possibility to infer the return type, now or in the future? Anybody else opposed to using positional arguments (which appeared to be the consensus on #12953 -- but I'm fine with either)?

@timholy
Copy link
Member

timholy commented Nov 23, 2015

The key is to infer the length of the created array's size tuple:

function rpt{N}(A::AbstractArray, outer::NTuple{N,Int})
    sz = rptsz(A, outer)
    Arpt = Array(eltype(A), sz)
    ...
end

This is pretty easy to write using generated functions, because in a generated function you can do things like M<N where M and N are two integer type parameters. However, various folks have advanced arguments that we should be doing more without generated functions, so in that spirit:

rptsz(A, ::Tuple{}) = size(A)
rptsz(A, outer::Tuple) = fill_length((), _rptsz((), A, outer), size(A))
# Build up the sz tuple so that we've consumed all elements of outer
@inline _rptsz{N}(sz::NTuple{N}, A::AbstractArray, ::Tuple{}) = sz
@inline _rptsz{N}(sz::NTuple{N}, A::AbstractArray, outer) = _rptsz((sz..., outer[1]*size(A,N+1)), A, Base.tail(outer))
# Take the preferred tuple, filling in with elements of default if it is longer
@inline fill_length(out, preferred, default) = fill_length((out..., preferred[1]), Base.tail(preferred), Base.tail(default))
@inline fill_length(out, ::Tuple{}, ::Tuple{}) = out
@inline fill_length(out, preferred, ::Tuple{}) = (out..., preferred...)
@inline fill_length(out, ::Tuple{}, default) = (out..., default...)

All of those @inlines guarantee that this nested set of function calls will be resolved at compile time.

Demo:

julia> using Base.Test

julia> @inferred(rptsz(zeros(2,2), ()))
(2,2)

julia> @inferred(rptsz(zeros(2,2), (5,)))
(10,2)

julia> @inferred(rptsz(zeros(2,2), (5,3)))
(10,6)

julia> @inferred(rptsz(zeros(2,2), (5,3,2)))
(10,6,2)

@timholy
Copy link
Member

timholy commented Nov 23, 2015

In case it's not obvious from the code, the way this works is by progressively peeling off the first element of corresponding tuples. You start with out = () and preferred = (10,6,2) and one-by-one append the first element of preferred to out.

@nalimilan
Copy link
Member Author

Thanks, Tim! So it looks like moving to positional arguments is worth it. Is everybody OK with merging this?

@timholy
Copy link
Member

timholy commented Nov 27, 2015

To make sure it's clear to everyone, this version produces a result that's still not inferrable, but it will be easier to get there. If you'd like, we could merge this and then I can polish up the code above.

@nalimilan
Copy link
Member Author

Fine with me. Will merge soon.

@tkelman
Copy link
Contributor

tkelman commented Nov 29, 2015

That doesn't really answer the question of whether the same performance would eventually be achievable with keyword arguments, does it? This seems like a step backwards on the API.

@nalimilan
Copy link
Member Author

The broader issue about whether keyword arguments will be taken into account for type inference is indeed crucial here. All I can say is that in the past several functions were changed to use positional arguments for performance, but I would be very happy if this wasn't required anymore.

@timholy
Copy link
Member

timholy commented Nov 29, 2015

Keyword arguments interact with performance in two distinct ways:

  • Call performance This is important for functions that execute very quickly. For example, you wouldn't want to use keyword arguments with getindex.
  • Inferrability

I think for most uses of repeat, the first is irrelevant. Are there precedents for changing solely for the 2nd reason? EDIT: keep in mind that one can always use a function barrier on the results of repeat.

@simonster
Copy link
Member

pivot used to be a keyword argument for cholfact/qrfact but it's now a positional argument (Val{true}/Val{false}, which is even more of a hack) for return type inference.

@nalimilan
Copy link
Member Author

That said, performance for repeat isn't critical right now, so if we're sure type inference will work with keyword arguments will come at some point, it would be perfectly reasonable to leave it as-is.

@simonster
Copy link
Member

The problem is that it's not performance for repeat, it's performance for tight loops involving the array returned by repeat. I'm not convinced that doesn't matter.

@nalimilan
Copy link
Member Author

@JeffBezanson Any thoughts about this?

@JeffBezanson
Copy link
Member

I think we should keep the API the way we want it, and improve the compiler.

@nalimilan
Copy link
Member Author

OK, I'll update the PR to only loosen the signature.

@nalimilan nalimilan changed the title Use positional arguments for repeat() and loosen signature Loosen signature for repeat() May 13, 2016
@nalimilan
Copy link
Member Author

I've updated the PR to only loosen the signature, keeping keyword arguments. I leave type-inferrability for another PR -- let me know if you think it can be achieved (and how).

inner::Array{Int} = ones(Int, ndims(A)),
outer::Array{Int} = ones(Int, ndims(A)))
"""
repeat(A::AbstractArray; inner=(), outer=())
Copy link
Contributor

Choose a reason for hiding this comment

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

() doesn't seem to accurately reflect the real default?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, the old code used a similar "trick". The actual value ones(Int, ndims(A)) is a bit long, though not the end of the world. I've also tried another solution in which passing an empty collection is supported and is internally replaced with a vector of ones. Which one would you prefer?

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure which implementation should be preferred. In terms of making the default in the docs match the more complicated version, how about giving the default value some temporary variable name that gets explained in more detail in the docstring body?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think the additional complexity for the reader is worth it. ones(Int, ndims(A)) is almost as clear.

Copy link
Member Author

Choose a reason for hiding this comment

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

Other opinions?

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, I've put the actual signature in the docs, that's the most obvious solution. Should be good to go now.

Accept any AbstractArray as first argument, and any iterable
for inner and outer arguments. Use tuples by default rather
than arrays, and allow passing an empty collection to mean
no-op.
@nalimilan
Copy link
Member Author

No objections against merging?

@nalimilan nalimilan merged commit a7cf985 into master May 24, 2016
@nalimilan nalimilan deleted the nl/repeat branch May 24, 2016 08:11
nalimilan added a commit to JuliaLang/Compat.jl that referenced this pull request May 24, 2016
Compatibility for JuliaLang/julia#14082. Use collect() since
convert(Array, x) does not work for tuples.
nalimilan added a commit to JuliaLang/Compat.jl that referenced this pull request May 24, 2016
Compatibility for JuliaLang/julia#14082. Since the new version in
Julia 0.5 calls similar() internally, it is not enough to wrap it
into a compatibility function to backport the fix. This is required
to deprecate rep() from DataArrays.jl, which provides similar
functionality for two custom array types.
nalimilan added a commit to JuliaLang/Compat.jl that referenced this pull request May 24, 2016
Compatibility for JuliaLang/julia#14082. Since the new version in
Julia 0.5 calls similar() internally, it is not enough to wrap it
into a compatibility function to backport the fix. This is required
to deprecate rep() from DataArrays.jl, which provides similar
functionality for two custom array types.
nalimilan added a commit to JuliaLang/Compat.jl that referenced this pull request May 24, 2016
Compatibility for JuliaLang/julia#14082. Since the new version in
Julia 0.5 calls similar() internally, it is not enough to wrap it
into a compatibility function to backport the fix. This is required
to deprecate rep() from DataArrays.jl, which provides similar
functionality for two custom array types.
nalimilan added a commit to JuliaLang/Compat.jl that referenced this pull request May 24, 2016
Compatibility for JuliaLang/julia#14082. Since the new version in
Julia 0.5 calls similar() internally, it is not enough to wrap it
into a compatibility function to backport the fix. This is required
to deprecate rep() from DataArrays.jl, which provides similar
functionality for two custom array types.
nalimilan added a commit to JuliaLang/Compat.jl that referenced this pull request May 24, 2016
Compatibility for JuliaLang/julia#14082. Since the new version in
Julia 0.5 calls similar() internally, it is not enough to wrap it
into a compatibility function to backport the fix. This is required
to deprecate rep() from DataArrays.jl, which provides similar
functionality for two custom array types.
nalimilan added a commit to JuliaLang/Compat.jl that referenced this pull request May 24, 2016
Compatibility for JuliaLang/julia#14082. Since the new version in
Julia 0.5 calls similar() internally, it is not enough to wrap it
into a compatibility function to backport the fix. This is required
to deprecate rep() from DataArrays.jl, which provides similar
functionality for two custom array types.
nalimilan added a commit to JuliaLang/Compat.jl that referenced this pull request May 24, 2016
Compatibility for JuliaLang/julia#14082. Since the new version in
Julia 0.5 calls similar() internally, it is not enough to wrap it
into a compatibility function to backport the fix. This is required
to deprecate rep() from DataArrays.jl, which provides similar
functionality for two custom array types.
nalimilan added a commit to JuliaLang/Compat.jl that referenced this pull request May 25, 2016
Compatibility for JuliaLang/julia#14082. Since the new version in
Julia 0.5 calls similar() internally, it is not enough to wrap it
into a compatibility function to backport the fix. This is required
to deprecate rep() from DataArrays.jl, which provides similar
functionality for two custom array types.
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.

5 participants