# Idiom for related types?

**URL:** <https://discourse.julialang.org/t/idiom-for-related-types/114706>\
**Category:** General Usage\
**Created:** [May 24, 2024, 10:11pm UTC](https://discourse.julialang.org/t/idiom-for-related-types/114706 "2024-05-24T22:11:56Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![slawrence](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/slawrence/32/203840_2.png) [@slawrence](https://discourse.julialang.org/u/slawrence)\
**Post date:** [May 24, 2024, 10:11pm UTC](https://discourse.julialang.org/t/idiom-for-related-types/114706/1 "2024-05-24T22:11:56Z")

</div>

**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

```julia
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

```julia
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:

```julia
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

```julia
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:

```julia
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

```julia
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.

```julia
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

```julia
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.

---

<div class="post-metadata">

**Author:** ![aryavorskiy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aryavorskiy/32/43466_2.png) [@aryavorskiy](https://discourse.julialang.org/u/aryavorskiy)\
**Post date:** [May 24, 2024, 10:26pm UTC](https://discourse.julialang.org/t/idiom-for-related-types/114706/2 "2024-05-24T22:26:52Z")

</div>

> [@slawrence](#):
>
> ```julia
> abstract type Leg end
> struct CatLeg <: Leg #= stuff =# end
> struct DogLeg <: Leg #= very different stuff =# end
> 
> ```

```julia
abstract type Leg{AnimalT <: Animal} end
struct CatLeg <: Leg{Cat} #= stuff =# end
struct DogLeg <: Leg{Dog} #= very different stuff =# end

```

Will this do the thing you need?

---

<div class="post-metadata">

**Author:** ![slawrence](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/slawrence/32/203840_2.png) [@slawrence](https://discourse.julialang.org/u/slawrence)\
**Post date:** [May 24, 2024, 10:31pm UTC](https://discourse.julialang.org/t/idiom-for-related-types/114706/3 "2024-05-24T22:31:52Z")

</div>

That might be the best thing. I’m still missing a capability I’d like:

```julia
function growleg(a::A) where {A <: Animal}
    # I want to call the constructor of either CatLeg or DogLeg
    return Leg{A}(a) # Nope, this is an error obviously
end

```

More abstractly, in what you wrote, it’s possible to have two different structs each of which is `<: Leg{Cat}` (and this is essentially why `Leg{A}(a)` isn’t correct). But conceptually that’s not the case, there’s a single struct which corresponds to `Leg{Cat}`, and so there ought to be a function that maps `Cat` to `CatLeg`, and `Dog` to `DogLeg`.

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [May 24, 2024, 10:32pm UTC](https://discourse.julialang.org/t/idiom-for-related-types/114706/4 "2024-05-24T22:32:19Z")

</div>

```julia-repl
julia> struct MyStruct{T}
           fields::T
           MyStruct(a::Int, b::Int) = new{Tuple{Int,Int}}((a,b))
           MyStruct(a::String, b::String, c::String) = new{Tuple{String,String,String}}((a,b,c))
       end

julia> MyStruct(3,5)
MyStruct{Tuple{Int64, Int64}}((3, 5))

julia> foo = MyStruct(3,5)
MyStruct{Tuple{Int64, Int64}}((3, 5))

julia> foo.a
3

julia> foo.b
5

julia> foo.c

julia> function Base.getproperty(ms::MyStruct{Tuple{Int,Int}}, s::Symbol)
           if s == :a
               getfield(ms, :fields)[1]
           elseif s == :b
               getfield(ms, :fields)[2]
           else
               error("No field named $s.")
           end
       end

julia> function Base.getproperty(ms::MyStruct{Tuple{String,String,String}}, s::Symbol)
           if s == :a
               getfield(ms, :fields)[1]
           elseif s == :b
               getfield(ms, :fields)[2]
           elseif s ==:c
               getfield(ms, :fields)[3]
           else
               error("No field named $s.")
           end
       end

julia> foo = MyStruct(3,5)
MyStruct{Tuple{Int64, Int64}}((3, 5))

julia> foo.a
3

julia> foo.b
5

julia> foo.c
ERROR: No field named c.

julia> bar = MyStruct("Walk", "The", "Dog")
MyStruct{Tuple{String, String, String}}(("Walk", "The", "Dog"))

julia> bar.a
"Walk"

julia> bar.b
"The"

julia> bar.c
"Dog"

julia> bar.d
ERROR: No field named d.

```

---

<div class="post-metadata">

**Author:** ![aryavorskiy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aryavorskiy/32/43466_2.png) [@aryavorskiy](https://discourse.julialang.org/u/aryavorskiy)\
**Post date:** [May 24, 2024, 10:37pm UTC](https://discourse.julialang.org/t/idiom-for-related-types/114706/5 "2024-05-24T22:37:38Z")

</div>

> [@slawrence](#):
>
> `return Leg{A}(a) # Nope, this is an error obviously`

Actually, not if you define this constructor for the abstract type 🙂

```julia
Leg{<:Cat}(A) = CatLeg(A)

```

---

<div class="post-metadata">

**Author:** ![slawrence](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/slawrence/32/203840_2.png) [@slawrence](https://discourse.julialang.org/u/slawrence)\
**Post date:** [May 24, 2024, 10:40pm UTC](https://discourse.julialang.org/t/idiom-for-related-types/114706/6 "2024-05-24T22:40:22Z")

</div>

> [@aryavorskiy](#):
>
> Actually, not if you define this constructor for the abstract type 🙂
> 
> `Leg{<:Cat}(A) = CatLeg(A)`

Okay I think this solves it. I’d played with defining constructors in weird ways but for some reason this hadn’t occurred to me. Thank you very much!
