# \[ANN\] DeprecateKeywords.jl – a macro for keyword deprecation

**URL:** <https://discourse.julialang.org/t/ann-deprecatekeywords-jl-a-macro-for-keyword-deprecation/100926>\
**Category:** Package Announcements\
**Created:** [June 28, 2023, 10:47am UTC](https://discourse.julialang.org/t/ann-deprecatekeywords-jl-a-macro-for-keyword-deprecation/100926 "2023-06-28T10:47:45Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [June 28, 2023, 10:47am UTC](https://discourse.julialang.org/t/ann-deprecatekeywords-jl-a-macro-for-keyword-deprecation/100926/1 "2023-06-28T10:47:46Z")

</div>

# [DeprecateKeywords.jl](https://github.com/MilesCranmer/DeprecateKeywords.jl)

[DeprecateKeywords.jl](https://github.com/MilesCranmer/DeprecateKeywords.jl) is a tiny package (77 lines) which defines a macro `@depkws` for keyword deprecation. (I wasn’t sure if I should even post this since it’s such a tiny macro, but I thought I might as well.)

While normally you can use `Base.@deprecate` for deprecating functions and _arguments_, because multiple dispatch does not apply to keywords, you actually need to manipulate the original function signature.

For example, let’s say we wish to deprecate the keyword `old_kw1` in favor of `new_kw1`, and  
and `old_kw2` in favor of `new_kw2`:

```julia
using DeprecateKeywords

@depkws function foo(; new_kw1=2, new_kw2=3,
                       @deprecate(old_kw1, new_kw1),
                       @deprecate(old_kw2, new_kw2))
    return new_kw1 + new_kw2
end

```

The use of normal `@deprecate` in here is syntactic sugar to help make the signature more intuitive. The `@depkws` will simply consume the `@deprecate`s and and interpret their contents.

With this, we can use both the old and new keywords. If using the old keyword, it will automatically be passed to the new keyword, but with a deprecation warning.

```julia
julia> foo(new_kw1=1, new_kw2=2)
3

julia> foo(old_kw1=1, new_kw2=2)
┌ Warning: Keyword argument `old_kw1` is deprecated. Use `new_kw1` instead.
│ caller = top-level scope at REPL[5]:1
└ @ Core REPL[5]:1
3

```

(The warning uses `depwarn`, so is only visible if one starts with `--depwarn=yes`)

Here’s what this actually gets expanded to:

```julia
function foo(; old_kw2 = DeprecatedDefault, old_kw1 = DeprecatedDefault, new_kw1 = begin
                  if old_kw1 !== DeprecatedDefault
                      Base.depwarn("Keyword argument `old_kw1` is deprecated. Use `new_kw1` instead.", :foo)
                      old_kw1
                  else
                      2
                  end
              end, new_kw2 = begin
                  if old_kw2 !== DeprecatedDefault
                      Base.depwarn("Keyword argument `old_kw2` is deprecated. Use `new_kw2` instead.", :foo)
                      old_kw2
                  else
                      3
                  end
              end)
      new_kw1 + new_kw2
  end

```

(i.e., it’s not doing anything sophisticated and 99% of the time you could just do this by hand. I basically just wanted a macro to help lower the mental barrier for keyword deprecation for my libraries.)

* * *

### Other notes/warnings

- I’m not 100% sure if/when this might prevent Julia from specializing to types, or if it is different from how you would set this up manually. So just be wary of major type inference issues when passing the deprecated keywords.
- This does not check whether the user passes both keyword arguments. (It might be better to use `kws...` and then pass through the old keywords within the function body. I didn’t do this in my current approach so that the user could still use `kws...` in the signature if they wish.)
- This uses the very nice [MacroTools.jl](https://github.com/FluxML/MacroTools.jl) package to help make the macro generic.

Contributions very much appreciated. I’m also open to better syntax suggestions!

* * *

Edits:

- Changed default value for deprecated keywords to a custom symbol to avoid value collisions (Thanks @Ronis_BR)

---

<div class="post-metadata">

**Author:** ![Ronis\_BR](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ronis_br/32/50999_2.png) [@Ronis\_BR](https://discourse.julialang.org/u/Ronis_BR)\
**Post date:** [June 28, 2023, 12:37pm UTC](https://discourse.julialang.org/t/ann-deprecatekeywords-jl-a-macro-for-keyword-deprecation/100926/2 "2023-06-28T12:37:55Z")

</div>

Awesome! I had to do some strange things in PrettyTables.jl to deprecate KW arguments:

> <https://github.com/ronisbr/PrettyTables.jl/blob/master/src/deprecations.jl>

I looked very briefly to your expanded code and I have one question: what happened if the deprecated keyword is passed with the value `nothing`? For example, we can call `pretty_tables` with `hlines = nothing` to remove all horizontal lines. Would this package works if I try to deprecate `hlines` and the user assigns nothing to it?

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [June 28, 2023, 1:05pm UTC](https://discourse.julialang.org/t/ann-deprecatekeywords-jl-a-macro-for-keyword-deprecation/100926/3 "2023-06-28T13:05:38Z")

</div>

> [@Ronis\_BR](#):
>
> I looked very briefly to your expanded code and I have one question: what happened if the deprecated keyword is passed with the value `nothing`? For example, we can call `pretty_tables` with `hlines = nothing` to remove all horizontal lines. Would this package works if I try to deprecate `hlines` and the user assigns nothing to it?

Great point. Indeed in that case, the `hlines=nothing` example would not work. To fix this, perhaps I should create a new type to stand-in as a default that is guaranteed not to collide with other defaults? Something like

```julia
abstract type DeprecatedDefault end

function foo(; old_kw2 = DeprecatedDefault, old_kw1 = DeprecatedDefault, new_kw1 = begin
                  if old_kw1 !== DeprecatedDefault
                      Base.depwarn("Keyword argument `old_kw1` is deprecated. Use `new_kw1` instead.", :foo)
                      old_kw1
                  else
                      2
                  end
              end, new_kw2 = begin
                  if old_kw2 !== DeprecatedDefault
                      Base.depwarn("Keyword argument `old_kw2` is deprecated. Use `new_kw2` instead.", :foo)
                      old_kw2
                  else
                      3
                  end
              end)
      new_kw1 + new_kw2
  end

```

Thoughts?

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [June 28, 2023, 1:21pm UTC](https://discourse.julialang.org/t/ann-deprecatekeywords-jl-a-macro-for-keyword-deprecation/100926/4 "2023-06-28T13:21:42Z")

</div>

Fixed!

> <https://github.com/MilesCranmer/DeprecateKeywords.jl/pull/5>
>
> Thanks to @ronisbr for pointing out this issue.

Thanks @Ronis_BR for bringing this up

---

<div class="post-metadata">

**Author:** ![Ronis\_BR](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ronis_br/32/50999_2.png) [@Ronis\_BR](https://discourse.julialang.org/u/Ronis_BR)\
**Post date:** [June 28, 2023, 2:26pm UTC](https://discourse.julialang.org/t/ann-deprecatekeywords-jl-a-macro-for-keyword-deprecation/100926/5 "2023-06-28T14:26:00Z")

</div>

Perfect! Thanks @MilesCranmer !
