Why does append!(df1, df2) return a DataFrame, as well as modify the argument df1?

Functions usually either modify one of their arguments, or do not modify their arguments and return a new value.

Normally you would expect one of the two semantics:

isnothing(sort!(df1, df2))
# df1 modified, `nothing` returned

or

df3 = sort(df1, df2)
# neither df1, df2 modified, new value returned

This is not a Julia specific thing - it’s just sort of how most languages / APIs work.