@unpack with Parameters.jl when key is not found

Hi, is there an easy way to have Parameters.jl skip missing keys or fields?

using Parameters
mydict = Dict(a=>3,b=>2.3,c=>"mystr")
@unpack (a,b,c,d) = mydict
ERROR: KeyError: key "d" not found

I would like it to just skip over the missing key. The reason I want this behavior is I am carrying around two lists which hold fixed parameters and ones to be found via optimization. It would be nice if I could just unpack them, without having to check which ones were in which list like:

@unpack (fixed1,fixed2,...) = fixedParams
@unpack (fitted1,fitted2,...) fittedParams

and instead being able to just say

@unpack (p1,p2,...) = fixedParams
@unpack (p1,p2,...) = fittedParams

I don’t think so. Part of the reason @unpack is fast is because it doesn’t have these kinds of checks. A macro doesn’t know what’s inside a Dict.

1 Like

Is there a way to write my own method then that does this check? It doesn’t need to be fast.

UnPack.jl is actually super simple once you understand metaprogramming.

https://github.com/mauro3/UnPack.jl/blob/master/src/UnPack.jl

In particular, lines 32-36 are what you would want to change. Instead of getindex you would want get(d, x, nothing) or similar.

3 Likes

I tried adding both of the following methods:

@inline unpack(x::AbstractDict{<:AbstractString}, k) = get(x,string(k),nothing)
@inline unpack(x::AbstractDict{<:AbstractString}, ::Val{k}) where {k} = get(x,string(k),nothing)

but still got the same error:

test=Dict("a"=>1,"b"=>2,"c"=>"mystr")
@unpack (a,b,c,d) = test
ERROR: KeyError: key "d" not found

Could you please tell us more about what steps you did to add the methods? Did you add the package for development using ] dev?

Ah sorry. I thought I was able to add methods similar to how I can add methods in Base.jl. So I was just adding them inline in my Julia script.

You can, but you should import methods first.
But this is type piracy and shouldn’t be used if you are writing a library (but more or less fine in your local script)

Here is an MWE

julia> using UnPack

julia> import UnPack: unpack

help?> unpack
search: unpack UnPack @unpack

  This function is invoked to unpack one field/entry of some DataType dt and has signature:

  unpack(dt::Any, ::Val{sym}) -> value of sym

  The sym is the symbol of the assigned variable.

  Three definitions are included in the package to unpack a composite type or a dictionary with Symbol
  or string keys:

  @inline unpack(x, ::Val{f}) where {f} = getproperty(x, f)
  @inline unpack(x::AbstractDict{Symbol}, ::Val{k}) where {k} = x[k]
  @inline unpack(x::AbstractDict{S}, ::Val{k}) where {S<:AbstractString,k} = x[string(k)]


  More methods can be added to allow for specialized unpacking of other datatypes.

  See also pack!.

julia> unpack(x::AbstractDict{S}, ::Val{k}) where {S<:AbstractString,k} = get(x, string(k), nothing)
unpack (generic function with 3 methods)

julia> d = Dict(["a" => 1])
Dict{String, Int64} with 1 entry:
  "a" => 1

julia> @unpack a, c = d
Dict{String, Int64} with 1 entry:
  "a" => 1

julia> c

That said, don’t do this… Maybe you can define your own macro, use @unpack’s parsing, but call your own function instead of unpack.

2 Likes

So how should one do this properly?

Fork @unpack and change the name of the macro?

I would like to use it as a convenience function in my own code. I mean, I still want to use the default Parameters.jl.

When I say “improper” it means that if you want

@unpack a = d

to not fail when a is not in d, you need to commit type piracy, which means overwriting a method you don’t own for a type you don’t own.

You can do that in your own code (see above), but the consequence is

  1. When people read your code, the behavior of @unpack will be different than they expect
  2. If other packages use your code in any way, existing uses of @unpack will behave differently than the original authors expected.

So you can’t be “correct” and change the behavior of @unpack.

1 Like