# Composition vs. OO: what's the best way to do this in Julia

**URL:** <https://discourse.julialang.org/t/composition-vs-oo-whats-the-best-way-to-do-this-in-julia/70504>\
**Category:** New to Julia\
**Created:** [October 27, 2021, 7:40pm UTC](https://discourse.julialang.org/t/composition-vs-oo-whats-the-best-way-to-do-this-in-julia/70504 "2021-10-27T19:40:12Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![dodoplus](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dodoplus/32/30148_2.png) [@dodoplus](https://discourse.julialang.org/u/dodoplus)\
**Post date:** [October 27, 2021, 7:40pm UTC](https://discourse.julialang.org/t/composition-vs-oo-whats-the-best-way-to-do-this-in-julia/70504/1 "2021-10-27T19:40:12Z")

</div>

Say I have 2 related types (previously seen in a 2016 thread with a similar topic):

```julia
struct Student
    age::Int
    name::AbstractString
    grade::Real
    function Student(a, n, g)
        0 < a < 120 || error("age must be in the range 0-120")
        0 < g < 5 || error("grade must be in the range 0-5")
        new(a, n, g)
    end
end

struct Employee
    age::Int
    name::AbstractString
    salary::Int
    function Employee(a, n, s)
        0 < a < 120 || error("age must be in the range 0-120")
        s > 20000 || error("salary is below minimum")
        new(a, n, s)
    end
end

```

(yes, this example is contrived)

These types share members _and_ some functionality. Obviously, I would want to reuse both.

Is this the best way to re-write this code?

```julia
struct PersonData
    age::Int
    name::AbstractString
    function PersonData(a, n)
        0 < a < 120 || error("age must be in the range 0-120")
        new(a,n)
    end
end 

age(pd::PersonData) = pd.age
#...

struct Student
    pd::PersonData
    grade::Real
    function Student(a, n, g)
        0 < g < 5 || error("grade must be in the range 0-5")
        new(PersonData(a,n),g)
    end
end
age(s::Student) = age(s.pd)
#...

struct Employee
    pd::PersonData
    salary::Int
    function Employee(a, n, s)
        s > 20000 || error("salary is below minimum")
        new(PersonData(a, n), s)
    end
end
age(e::Employee) = age(e.pd)
#...

```

---

<div class="post-metadata">

**Author:** ![gbaraldi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gbaraldi/32/22101_2.png) [@gbaraldi](https://discourse.julialang.org/u/gbaraldi)\
**Post date:** [October 27, 2021, 7:45pm UTC](https://discourse.julialang.org/t/composition-vs-oo-whats-the-best-way-to-do-this-in-julia/70504/2 "2021-10-27T19:45:52Z")

</div>

Looks good to me  
One thing, don’t have fields with abstract types because that is quite bad for performance. You could either just use concrete types like instead of Real use Float64. Or use parametric types like described here [Types · The Julia Language](https://docs.julialang.org/en/v1/manual/types/#Parametric-Types) .

---

<div class="post-metadata">

**Author:** ![tchr](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tchr/32/15138_2.png) [@tchr](https://discourse.julialang.org/u/tchr)\
**Post date:** [October 28, 2021, 12:15am UTC](https://discourse.julialang.org/t/composition-vs-oo-whats-the-best-way-to-do-this-in-julia/70504/3 "2021-10-28T00:15:27Z")

</div>

I think [@wsshin’s suggestion](https://github.com/JuliaLang/julia/issues/4935#issuecomment-876746008) from the [abstract types with fields](https://github.com/JuliaLang/julia/issues/4935) issue is a nice alternative: effectively, it inverts the embedding order of the shared fields.

---

<div class="post-metadata">

**Author:** ![dodoplus](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dodoplus/32/30148_2.png) [@dodoplus](https://discourse.julialang.org/u/dodoplus)\
**Post date:** [October 28, 2021, 6:14am UTC](https://discourse.julialang.org/t/composition-vs-oo-whats-the-best-way-to-do-this-in-julia/70504/4 "2021-10-28T06:14:36Z")

</div>

That’s a nice trick.  
So, I can rewrite my code to:

```julia
abstract type PersonSpecs end

struct PersonData{S<:PersonSpecs}
    s::PersonSpecs
    age::Int
    name::String
end

struct StudentSpecs <: PersonSpecs
    grade::Real
end

struct EmployeeSpecs <: PersonSpecs
    salary::Int
end

const Student = PersonData{StudentSpecs}
const Employee = PersonData{EmployeeSpecs}

```

However, I’m missing the constructors.  
I can try:

```julia
struct PersonData{S<:PersonSpecs}
    s::S
    age::Int
    name::String
    function PersonData(s, a, n)
        0 < a < 120 || error("age must be in the range 0-120")
        new{typeof(s)}(s, a, n)
    end
end

struct StudentSpecs <: PersonSpecs
    grade::Real
    function StudentSpecs( g)
        0 < g < 5 || error("grade must be in the range 0-5")
        new(g)
    end
end

struct EmployeeSpecs <: PersonSpecs
    salary::Int

    function Employee(a, n, s)
        s > 20000 || error("salary is below minimum")
        new(s)
    end
end

```

However if I now try:

```julia
s = Student(StudentSpecs(4.5), 19, "John")

```

I get:

```julia
MethodError: no method matching Student(::StudentSpecs, ::Int64, ::String)

```

(also, this is quite ugly, but I guess I can wrap this in a helper function, e.g. `new_student`)

What am I missing?

---

<div class="post-metadata">

**Author:** ![ffevotte](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffevotte/32/6587_2.png) [@ffevotte](https://discourse.julialang.org/u/ffevotte)\
**Post date:** [October 28, 2021, 8:51am UTC](https://discourse.julialang.org/t/composition-vs-oo-whats-the-best-way-to-do-this-in-julia/70504/5 "2021-10-28T08:51:59Z")

</div>

I think the standard way of defining the [parametric constructor](https://docs.julialang.org/en/v1/manual/constructors/#Parametric-Constructors) would look like:

```julia
    function PersonData{S}(s::S, a, n) where {S}
        0 < a < 120 || error("age must be in the range 0-120")
        new(s, a, n)
    end

```

With such a definition, the “ugly” construction works:

```julia
julia> Student(StudentSpecs(4.5), 19, "John")
Student(StudentSpecs(4.5), 19, "John")

```

And you can wrap this in another, prettier, constructor such as

```julia
# Needs to be specific enough to avoid method ambiguities
# (but this probably wouldn't be a problem if there were several fields in `StudentSpecs`)
Student(grade::Real, args...) = Student(StudentSpecs(grade), args...)

```

which yields:

```julia
julia> Student(4.5, 19, "John")
Student(StudentSpecs(4.5), 19, "John")

```

> **complete working example**
>
> abstract type PersonSpecs end
> 
> struct PersonData{S\<:PersonSpecs}  
> s::S  
> age::Int  
> name::String  
> function PersonData{S}(s::S, a, n) where {S}  
> 0 \< a \< 120 || error(“age must be in the range 0-120”)  
> new(s, a, n)  
> end  
> end
> 
> struct StudentSpecs \<: PersonSpecs  
> grade::Real  
> function StudentSpecs(g)  
> 0 \< g \< 5 || error(“grade must be in the range 0-5”)  
> new(g)  
> end  
> end
> 
> struct EmployeeSpecs \<: PersonSpecs  
> salary::Int
> 
> ```
> function EmployeeSpecs(s)
> s > 20000 || error("salary is below minimum")
> new(s)
> end
> 
> ```
> 
> end
> 
> const Student = PersonData{StudentSpecs}  
> Student(grade::Real, args…) = Student(StudentSpecs(grade), args…)
> 
> const Employee = PersonData{EmployeeSpecs}  
> Employee(salary::Int, args…) = Employee(EmployeeSpecs(salary), args…)
> 
> Student(4.5, 19, “John”)  
> Employee(21\_000, 19, “John”)  
> Employee(19\_000, 19, “John”)
