Type system abuse, advisability of

This isn’t abuse of the type system, sometimes it’s useful to put data into the type domain.

Some stylistic notes:

  1. usually the constructors would be organized in such a manner that there’s at most a single explicit inner constructor:
struct Fix{Fn,Args,KW}
  Fix{Fn,Args,KW}() where {Fn,Args,KW} = new{Fn,Args,KW}()
end
Fix{Fn,Args}() where {Fn,Args} = Fix{Fn,Args,(;)}()
Fix{Fn}() where {Fn} = Fix{Fn,()}()
  1. The definition of the inner constructor in the above definition of Fix is actually redundant: leaving out the definition of the inner constructor would be preferable in this case because Julia will do the right thing by creating the default constructor. An inner constructor is most useful for checking the arguments given to the constructor and similar, which isn’t necessary here.
julia> struct Fix{Fn,Args,KW} end

julia> methods(Fix)
# 0 methods for type constructor

julia> methods(Fix{rand})
# 0 methods for type constructor

julia> methods(Fix{rand, (), (;)})
# 1 method for type constructor:
 [1] (var"#ctor-self#"::Type{Fix{Fn, Args, KW}} where {Fn, Args, KW})()
     @ REPL[1]:1
  1. The intention behind a constructor like Fix{Fn}() where {Fn} = Fix{Fn,()}() is to provide a default value for a type parameter. This makes sense and works, but may be problematic in some cases, because it is, in a sense, ambiguous, because it may not be clear whether an expression like Fix{some_function} should refer to the UnionAll type (see here and here) or to the constructor method. This wouldn’t be an issue in practice for Fix, but it doesn’t seem like good style. It may be better to have the user-facing interface be a function that would wrap the Fix type, which would have only the inner constructor.
2 Likes