Easy way to define functions with specific NamedTuple arguments?


#1

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 .


#2

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.


#3

I have a small package

which allows you to use

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

#4

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.


#5

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

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


#6

Yes, Bayesian analysis was exactly my use case (for https://github.com/bat/BAT.jl). :slight_smile:

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