How to use generic constructors in interface modules?

I’m writing an interface module that contains a function that transforms any subtype of a given user-defined abstract type. I’m unclear how to write this in an interface module, since the constructor of the concrete collection is unknown to the interface.

For example: multiplication of each element by a number. It should return a collection of the same type, but whose elements have been multiplied. I want something like:

abstract type MyAbstractCollection end 
function *(collection::T, x::Number) where {T<:MyAbstractCollection} 
    return constructor(collection, x * elt for elt in collection)
end

The only thing I can think to do is require that concrete subtypes of MyAbstractCollection implement the constructor method. Is there a better/more Julian way?

It may be related to this Julialang issue?

Assuming collection isn’t broadcastable: Is constructor a necessary function name? I would normally assume the concrete T itself implements a constructor method.

Assuming collection is broadcastable: you could just do return x .* collection. I don’t know what you have in mind for MyAbstractCollection and have never messed with the array and broadcasting interfaces, so I don’t know how you could make collection broadcastable.

Semantics and broadcastability aside, I think you could implement some stuff for the abstract type, but each concrete type has to implement something for itself. Ideally you design the abstract stuff to depend on as few concrete-type methods as possible.

Thanks. The problem is that the constructor for collection has an unknown API: a concrete subtype may require additional fields such as:

struct ConcreteCollection{T}
    items::Vector{T}
    metadata::Vector{String} 
end

While you could make a method constructor(collection::T, args...), I’d expect ConcreteCollection(args...) because typically a concrete subtype will implement constructor methods with the same name as the struct.

Additionally, I think constructor's first argument collection only provides type information because (x * elt for elt in collection) should provide all the data. A ConcreteCollection method would be inherently specific to the type so it wouldn’t need the first argument at all.

This bit has nothing to do with removing the need to implement something for each concrete type, either way you would do just that.

Essentially, the issue is how to mutate an immutable struct, as I understand it.
Setfield.jl may solve your problem.

https://jw3126.github.io/Setfield.jl/stable/intro/

Thanks for the suggestion. I tried it but it seems to fail for cases where some fields are generated automatically by inner constructors, which happens to be my use-case.

julia> using Setfield

julia> struct Toy
           a::Float64
           a2::Float64
           Toy(a::Float64)= new(a,a^2) 
       end

julia> toy= Toy(3.0)
Toy(3.0, 9.0)

julia> toy1= @set toy.a=4.0
ERROR: MethodError: no method matching Toy(::Float64, ::Float64)
Closest candidates are:
  Toy(::Float64) at none:4
...

I see a couple of options, depending on exactly what your struct looks like.

If it’s like your first example

struct ConcreteCollection{T}
    items::Vector{T}
    metadata::Vector{String} 
end

then items is actually mutable. So you could change your updating functions to make a copy (or receive one as input) and modify items in place. That would not enforce any restrictions that the inner constructor may want to enforce.

If it’s more like your second example where the fields are immutable but the inner constructor needs to enforce a cross-field restriction, one option would be to drop the a2 field and compute it as needed (if that does not cost too much performance). Then Setfield would work again.

I can’t tell whether either would work in your case based on the info that I have.