@@ -106,10 +106,14 @@ explicitly.
106106New types should prefer to register their default algorithms in the type domain.
107107""" default_algorithm
108108default_algorithm (f:: F , A; kwargs... ) where {F} = default_algorithm (f, typeof (A); kwargs... )
109+ default_algorithm (f:: F , A, B; kwargs... ) where {F} = default_algorithm (f, typeof (A), typeof (B); kwargs... )
109110# avoid infinite recursion:
110111function default_algorithm (f:: F , :: Type{T} ; kwargs... ) where {F,T}
111112 throw (MethodError (default_algorithm, (f, T)))
112113end
114+ function default_algorithm (f:: F , :: Type{TA} , :: Type{TB} ; kwargs... ) where {F,TA,TB}
115+ throw (MethodError (default_algorithm, (f, TA, TB)))
116+ end
113117
114118@doc """
115119 copy_input(f, A)
@@ -153,28 +157,8 @@ macro algdef(name)
153157 end )
154158end
155159
156- """
157- @functiondef f
158-
159- Convenience macro to define the boilerplate code that dispatches between several versions of `f` and `f!`.
160- By default, this enables the following signatures to be defined in terms of
161- the final `f!(A, out, alg::Algorithm)`.
162-
163- ```julia
164- f(A; kwargs...)
165- f(A, alg::Algorithm)
166- f!(A, [out]; kwargs...)
167- f!(A, alg::Algorithm)
168- ```
169-
170- See also [`copy_input`](@ref), [`select_algorithm`](@ref) and [`initialize_output`](@ref).
171- """
172- macro functiondef (f)
173- f isa Symbol || throw (ArgumentError (" Unsupported usage of `@functiondef`" ))
174- f! = Symbol (f, :! )
175-
176- ex = quote
177- # out of place to inplace
160+ function _arg_expr (:: Val{1} , f, f!)
161+ return quote # out of place to inplace
178162 $ f (A; kwargs... ) = $ f! (copy_input ($ f, A); kwargs... )
179163 $ f (A, alg:: AbstractAlgorithm ) = $ f! (copy_input ($ f, A), alg)
180164
@@ -215,7 +199,110 @@ macro functiondef(f)
215199 # copy documentation to both functions
216200 Core. @__doc__ $ f, $ f!
217201 end
218- return esc (ex)
202+ end
203+
204+ function _arg_expr (:: Val{2} , f, f!)
205+ return quote
206+ # out of place to inplace
207+ $ f (A, B; kwargs... ) = $ f! (copy_input ($ f, A, B)... ; kwargs... )
208+ $ f (A, B, alg:: AbstractAlgorithm ) = $ f! (copy_input ($ f, A, B)... , alg)
209+
210+ # fill in arguments
211+ function $f! (A, B; alg= nothing , kwargs... )
212+ return $ f! (A, B, select_algorithm ($ f!, (A, B), alg; kwargs... ))
213+ end
214+ function $f! (A, B, out; alg= nothing , kwargs... )
215+ return $ f! (A, B, out, select_algorithm ($ f!, (A, B), alg; kwargs... ))
216+ end
217+ function $f! (A, B, alg:: AbstractAlgorithm )
218+ return $ f! (A, B, initialize_output ($ f!, A, B, alg), alg)
219+ end
220+
221+ # define fallbacks for algorithm selection
222+ @inline function select_algorithm (:: typeof ($ f), A, alg:: Alg ; kwargs... ) where {Alg}
223+ return select_algorithm ($ f!, A, alg; kwargs... )
224+ end
225+ # define default algorithm fallbacks for out-of-place functions
226+ # in terms of the corresponding in-place function
227+ @inline function default_algorithm (:: typeof ($ f), A, B; kwargs... )
228+ return default_algorithm ($ f!, A, B; kwargs... )
229+ end
230+ # define default algorithm fallbacks for out-of-place functions
231+ # in terms of the corresponding in-place function for types,
232+ # in principle this is covered by the definition above but
233+ # it is necessary to avoid ambiguity errors with the generic definitions:
234+ # ```julia
235+ # default_algorithm(f::F, A; kwargs...) where {F} = default_algorithm(f, typeof(A); kwargs...)
236+ # function default_algorithm(f::F, ::Type{T}; kwargs...) where {F,T}
237+ # throw(MethodError(default_algorithm, (f, T)))
238+ # end
239+ # ```
240+ @inline function default_algorithm (:: typeof ($ f), :: Type{A} , :: Type{B} ; kwargs... ) where {A, B}
241+ return default_algorithm ($ f!, A, B; kwargs... )
242+ end
243+
244+ # copy documentation to both functions
245+ Core. @__doc__ $ f, $ f!
246+ end
247+ end
248+
249+ """
250+ @functiondef [n_args=1] f
251+
252+ Convenience macro to define the boilerplate code that dispatches between several versions of `f` and `f!`.
253+ By default, `f` accepts a single argument `A`. This enables the following signatures to be defined in terms of
254+ the final `f!(A, out, alg::Algorithm)`.
255+
256+ ```julia
257+ f(A; kwargs...)
258+ f(A, alg::Algorithm)
259+ f!(A, [out]; kwargs...)
260+ f!(A, alg::Algorithm)
261+ ```
262+
263+ The number of inputs can be set with the `n_args` keyword
264+ argument, so that
265+
266+ ```julia
267+ @functiondef n_args=2 f
268+ ```
269+
270+ would create
271+
272+ ```julia
273+ f(A, B; kwargs...)
274+ f(A, B, alg::Algorithm)
275+ f!(A, B, [out]; kwargs...)
276+ f!(A, B, alg::Algorithm)
277+ ```
278+
279+ See also [`copy_input`](@ref), [`select_algorithm`](@ref) and [`initialize_output`](@ref).
280+ """
281+ macro functiondef (args... )
282+ kwargs = map (args[1 : end - 1 ]) do kwarg
283+ if kwarg isa Symbol
284+ :($ kwarg = $ kwarg)
285+ elseif Meta. isexpr (kwarg, :(= ))
286+ kwarg
287+ else
288+ throw (ArgumentError (" Invalid keyword argument '$kwarg '" ))
289+ end
290+ end
291+ isempty (kwargs) || length (kwargs) == 1 || throw (ArgumentError (" Only one keyword argument to `@functiondef` is supported" ))
292+ f_n_args = 1 # default
293+ if length (kwargs) == 1
294+ kwarg = only (kwargs) # only one kwarg is currently supported, TODO modify if we support more
295+ key:: Symbol , val = kwarg. args
296+ key === :n_args || throw (ArgumentError (" Unsupported keyword argument $key to `@functiondef`" ))
297+ (isa (val, Integer) && val > 0 ) || throw (ArgumentError (" `n_args` keyword argument to `@functiondef` should be an integer > 0" ))
298+ f_n_args = val
299+ end
300+
301+ f = args[end ]
302+ f isa Symbol || throw (ArgumentError (" Unsupported usage of `@functiondef`" ))
303+ f! = Symbol (f, :! )
304+
305+ return esc (_arg_expr (Val (f_n_args), f, f!))
219306end
220307
221308"""
0 commit comments