Automatically parameterizing a type

In order to achieve good performance one is supposed to use parametric types. Is there a way to do this automatically?

I want this

@parameterize struct Foo
   field1::Real
   field2::AbstractVector
   field3::Number
end

become

 struct Foo{T1 <: Real, T2 <: AbstractVector, T3 <: Number}
   field1::T1
   field2::T2
   field3::T3
end

I use huge types with many fields for model simulations and it becomes very tedious to write this out by hand.

5 Likes

This feature could also be added to Parameters.jl. But it would also be nice to have this separately.

1 Like

Was a fun exercise:

macro params(struct_expr)
	name = struct_expr.args[2]
	fields = @view struct_expr.args[3].args[2:2:end]
	params = []
	for i in 1:length(fields)
		x = fields[i]
		if x isa Symbol
			T = gensym()
			push!(params, T)
			fields[i] = :($x::$T)
		end
	end
	if name isa Symbol && length(params) > 0
		struct_expr.args[2] = :($name{$(params...)})
	elseif name.head == :curly
		append!(struct_expr.args[2].args, params)
	else
		error("Unidentified type definition.")
	end

	esc(struct_expr)
end
julia> @params struct MyType
           a
           b::Int
           c
       end

julia> MyType{Int, Float64}(1, 1, 1.0)
MyType{Int64,Float64}(1, 1, 1.0)
1 Like

I realize the above is not exactly the requested feature so…here you go:

macro params(struct_expr)
   name = struct_expr.args[2]
   fields = @view struct_expr.args[3].args[2:2:end]
   params = []
   for i in 1:length(fields)
           x = fields[i]
           T = gensym()
           if x isa Symbol
                   push!(params, T)
                   fields[i] = :($x::$T)
           elseif x.head == :(::)
                   abstr = x.args[2]
                   var = x.args[1]
                   push!(params, :($T <: $abstr))
                   fields[i] = :($var::$T)
           end
   end
   if name isa Symbol && length(params) > 0
           struct_expr.args[2] = :($name{$(params...)})
   elseif name.head == :curly
           append!(struct_expr.args[2].args, params)
   else
           error("Unidentified type definition.")
   end

   esc(struct_expr)
end
julia> @params struct MyType
           a::Integer
           b
       end

julia> MyType(1,2)
MyType{Int64,Int64}(1, 2)

julia> MyType(1.0,2)
ERROR: MethodError: no method matching MyType(::Float64, ::Int64)
Closest candidates are:
  MyType(::##360<:Integer, ::##361) where {##360<:Integer, ##361} at REPL[2]:2
Stacktrace:
 [1] top-level scope at none:0
1 Like

Do you need them at all? I usually end up deleting those qualifiers anyway once I realise I need to use a unit, not Real, or that a tuple would do instead of the Vector. Unless you really need the type checks.

Writing packages, I initially I went with safety and explicit type checks everywhere. But now I keep struct field types as loose as possible and give the user a little more rope to hang themselves with.

I’ve had a lot of hassles with overly restricted types blocking the use of Dual numbers or units in other peoples packages. Having to write a pull request to remove types is way more of a pain than not getting an immediate warning that you are using the wrong type. But opinions on this may vary.

1 Like

Concrete struct field types are critical for performance (if the struct is used in inner loops). It’s not just about safety.

See also this explanation for why concrete fields matter.

2 Likes

Absolutely. But you don’t need <: Real etc for that, and it can be more of a pain than its worth.

I mean to do this:

 struct Foo{T1, T2, T3}
   field1::T1
   field2::T2
   field3::T3
end

But use better type variable names if you need documentation. Then it’s really not much extra writing and you can drop the macro.

@params struct Foo
   field1
   field2
   field3
end

is quite convenient, even without being more specific on the types, isn’t it?

I often have types with 10 or more fields. In these cases I’d really like to avoid writing out everything by hand.

Thanks, @mohamed82008, you solved my problem!

I’ve never worked with macros, so it would take a while to figure out how that works, though :sweat_smile:

I opened an issue with Parameters.jl linking to your solution. Would be great if this feature could be added to that package.

@qstruct_fp already does this https://github.com/cstjean/QuickTypes.jl

1 Like