Short version:
I want something that serves the role of “methods for structs”, where the content of the struct depends nontrivially on the parameter (just as the content of a method can depend on the type of an argument). Obviously I can have
struct MyStruct{T}
field_a::T
field_b::T
end
but it turns out that there are times when I want to have, say, two fields if T
is Int
and three if it’s String
. The closest I can come up with is
struct MyStructForInt #= blah blah blah =# end
struct MyStructForString #= totally different =# end
MyStruct(::Type{Int}) = MyStructForInt
MyStruct(::Type{String}) = MyStructForString
This feels unwieldy, like I shouldn’t do it and I’m going to regret the decision later.
I’m wondering if there’s something a little more natural/idiomatic.
And since that’s probably not very clear, here’s a good deal of elaboration, trying to explain how this comes about.
Long version:
I have a set of (conceptually) related types. The classic cartoon version is something like:
abstract type Animal end
struct Cat <: Animal end
struct Dog <: Animal end
Now I want to be able to write generic code that acts on any Animal
naturally. This is accomplished by defining a function with multiple methods
speak(::Cat) = println("meow")
speak(::Dog) = println("woof")
So far this is perfectly standard. Now suppose I want to write a function walk(::Animal)
. This might look like:
function walk(a::Animal)
for leg in legs(a)
move(leg)
end
end
Of course I need a definition for legs
. A cat’s leg is not the same object as a dog’s leg—for the purposes of this demonstration let’s assume that representing them requires different numbers of fields. Therefore different structs need to be defined, so I end up with
abstract type Leg end
struct CatLeg <: Leg #= stuff =# end
struct DogLeg <: Leg #= very different stuff =# end
legs(::Cat) = [CatLeg(), CatLeg(), CatLeg(), CatLeg()]
legs(::Dog) = [DogLeg(), DogLeg(), DogLeg(), DogLeg()]
function move(::CatLeg) #= move leg gracefully =# end
function move(::DogLeg) #= move leg less gracefully =# end
So far everything seems okay, although we might be worried that it’s not possible to add type annotations to the definition of walk
above: what’s the type of leg
?
Disaster strikes when I attempt to attach a fifth leg to the dog.
function addleg(a::Animal, l::Leg)
#= Do something =#
end
This is not what I mean! Obviously it’s only okay to add a DogLeg
to a Dog
and a CatLeg
to a cat. What I want to do is write something like
function addleg(a::A, l::Leg{A}) where {A <: Animal}
#= Do something =#
end
But here, Leg{Dog}
and Leg{Cat}
would be forced to have the same structure, which they don’t.
Hope that made some sense (and wasn’t too gruesome). The actual case I have has several related types, analogous to having Leg
and Paw
and Head
each defined for Dog
and Cat
and Elephant
, with radically different structure. Having a nice way to go from T <: Animal
to the appropriate Leg{T}
-like type would make life much easier.