Easy way to define functions with specific NamedTuple arguments?

Do we have an easy way to write functions that take specific kinds of named tuple as arguments? I mean writing a function

foo((a, b, c))

That will accept

foo((a = 42, b = 4.2, c = [2, 5]))

but not

foo((a = 4.2, b = 42, c = 25))

Typical use cases would be defining a fit function with named fit parameters (that the fitter flattens to resp. reconstructs from a flattened parameter vector).

This is kinda annoying to write:

foo((a, b, c)::NamedTuple{(:a, :b, :c), <:Tuple{Integer, Any, AbstractVector}}) = ...

I came up with this macro:

using MacroTools

macro ntargs(arguments)
    @capture(arguments, (capargs__,))

    argsyms = :(())
    ntsyms = :(())
    nttypes = :(Tuple{})

    for arg in capargs
        t = if arg isa Symbol
            push!(argsyms.args, arg)
            push!(ntsyms.args, QuoteNode(arg))
            push!(nttypes.args, :Any)
            :Any
        else
            @capture(arg, n_::t_) || error("Expected \"name::type\"")
            push!(argsyms.args, n)
            push!(ntsyms.args, QuoteNode(n))
            push!(nttypes.args, t)
        end
    end

    esc(:($argsyms::NamedTuple{$ntsyms,<:$nttypes}))
end

This allows you to write

foo(@ntargs a::Integer, b, c::AbstractVector) = a * b * c

foo((a = 42, b = 4.2, c = [2, 5]))

Do we already have something in this line? If not, I’ll add it to https://github.com/oschulz/ParameterShapes.jl .

For this specific case, something like:

julia> foo(a::NamedTuple) = _foo(;a...);

julia> _foo(; a::Integer, b::Any, c::AbstractVector) = "stuff";

julia> foo((a = 42, b = 4.2, c = [2, 5]))
"stuff"

julia> foo((a = 4.2, b = 42, c = 25))
ERROR: TypeError: in #_foo, in typeassert, expected Integer, got Float64

could work.

1 Like

I have a small package

https://github.com/tpapp/EponymTuples.jl/

which allows you to use

f(@eponymargs(a::Integer, b, c::AbstractVector)) = ...

Hah, I should have known you had something for that, Tamas. :slight_smile:

Very nice, thanks! This can up while I was developing ParameterShapes.jl (not registered yet), I hope that will somewhat complement your TransformVariables.jl, that’s why I was looking for such function defs.

It should work fine with TransformVariables syntax for NamedTuples, ie

as((a = ..., b = ..., c = ...))

(after all, it is just a macro expanding to valid syntax). Using it to program complex Bayesian models was my motivation for writing it.

The only issue you may run into is

https://github.com/JuliaDocs/DocStringExtensions.jl/issues/67

which is orthogonal to both packages and is a consequence of how destructuring works at the moment.

Yes, Bayesian analysis was exactly my use case (for GitHub - bat/BAT.jl: A Bayesian Analysis Toolkit in Julia). :slight_smile:

We’re planning to use your HMC sampler in BAT.jl too (in addition to Metropolis-Hastings).