JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.43k stars 5.45k forks source link

Better introduction to parametric types in the manual #43811

Open adigitoleo opened 2 years ago

adigitoleo commented 2 years ago

After a discussion in IRC today I realised that there were a few things about parametric types that aren't easily understood. It seems that the manual could help a bit more in introducing parametric types. I'm opening this issue to outline some of the things I think could be documented better, and also to gather ideas or examples that might help to write a more hands-on parametric types section in the manual. Not sure if this belongs on the forum but I am thinking of linking a PR at some point.

For the parametric type Foo and an instance foo, I think the docs could clarify

struct Foo{T}
    a::T
    b::T
end

struct Foo{T1, T2}
    a::T1
    b::T2
end

and (more visibly) inform

I also think there should be one example on how to set up a simple but complete type hierarchy (without going into all of the gritty details of the Advanced Types section). Something like the comment by wasshin in #4935.

There might be some other things I've forgotten, feel free to suggest related improvements. I feel that parametric types are one of the major features of Julia's type system, however they can be confusing when coming from languages with more traditional/simple OOP systems. Understanding parametric types should help to reduce common antipatterns like over-constraining argument types, and help people to leverage the dispatch system (what are all those <: needed for anyway?)

Preetham-Reddy-007 commented 2 years ago

@adigitoleo I would like to work on this issue, can you please guide me to get started to contribute to Julia. Thanks!

adigitoleo commented 2 years ago

@Preetham-Reddy-007 Thanks for your interest in contributing. Contributor guidelines are available, in particular see the checklist and the documentation section.

My idea when writing the issue was to improve the types page in the manual, focusing on the parametric types section. I don't yet completely understand the answers to all of the points I raised in the first post :smile:, so the first step would be to research Julia's parametric type implementation and come up with a concise overview.

Have a play around with the type system and see if you can understand it. Check for things that are surprising compared to what the manual says. For example,

not an example > All declared types (the DataType variety) can be parameterized makes it sound like only `DataType` subtypes can be parametrized, but check this out: ```julia julia> abstract type Bar{T} end julia> struct Barbarian{T} <: Bar{T} a::T b::T end julia> barbarian = Barbarian("axe", "axe") Barbarian{String}("axe", "axe") julia> barbarian isa Bar true julia> struct Bard{T} <: Bar{T} a::T b::T end julia> jaskier = Bard('ó', 'ñ') Bard{Char}('ó', 'ñ') julia> jaskier isa Bar true julia> Bar <: DataType false julia> struct Barf{T} <: Bar a::T b::T end ERROR: invalid subtyping in definition of Barf Stacktrace: [1] top-level scope @ REPL[10]:1 ```

EDIT: This was just me not getting it, Bar isa DataType is what I should have checked here.

adigitoleo commented 2 years ago

I just came across another package which once again defined an abstract container like (very simplified):

julia> struct Foo{T<:Real,N} <: AbstractArray{T,N}
           a::Array{T,N}
           b::String
       end

which leads to problems like:

julia> Foo(transpose([1, 2, 3, 4]), "wtf")
ERROR: MethodError: no method matching Foo(::LinearAlgebra.Transpose{Int64, Vector{Int64}}, ::String)
Closest candidates are:
  Foo(::Array{T, N}, ::String) where {T<:Real, N} at REPL[1]:2
Stacktrace:
 [1] top-level scope
   @ REPL[2]:1

The solution is:

julia> struct Foo{T<:AbstractArray}
           a::T
           b::String
       end

julia> Foo(transpose([1, 2, 3, 4]), "yay")
Foo{LinearAlgebra.Transpose{Int64, Vector{Int64}}}([1 2 3 4], "yay")

However, it looks a bit like a::T might be an abstract field type. There is valid reason to avoid abstract field types. This could scare people into using the first option. The good news is that the field type is not actually abstract in this case:

julia> foo = Foo(transpose([1, 2, 3, 4]), "yay")
Foo{LinearAlgebra.Transpose{Int64, Vector{Int64}}}([1 2 3 4], "yay")

julia> typeof(foo)
Foo{LinearAlgebra.Transpose{Int64, Vector{Int64}}}

julia> isconcretetype(typeof(foo.a))
true

I think this is one of the main things that deserves stronger demonstration. When people start using the first option, it leads down the rabbit hole of over-constraining of argument types to avoid a MethodError from the constructor.

adigitoleo commented 2 years ago

I've found that some of the relevant information is in the constructors section, there's even a case study example. However, the focus is more on advanced constructor logic.

adigitoleo commented 2 years ago

At the risk of overloading this thread, I'll also mention that the last example from the constructors section suffers from the same problem:

julia> struct SummedArray{T<:Number,S<:Number}
           data::Vector{T}
           sum::S
       end

julia> SummedArray([1; 2; 3], 6)
SummedArray{Int64, Int64}([1, 2, 3], 6)

julia> SummedArray(transpose([1; 2; 3]), 6)
ERROR: MethodError: no method matching SummedArray(::LinearAlgebra.Transpose{Int64, Vector{Int64}}, ::Int64)
Closest candidates are:
  SummedArray(::Vector{T}, ::S) where {T<:Number, S<:Number} at REPL[5]:2
Stacktrace:
 [1] top-level scope
   @ REPL[7]:1

I'm using transpose a lot, for demonstration, there are plenty of other methods which return SomeType <: AbstractArray where SomeType is not Array or Vector. Especially when you start to use packages.

simeonschaub commented 2 years ago

You seem to have a pretty clear idea for how you want that section to look like, would you you like to just propose a PR yourself? I do think this could definitely be useful.

adigitoleo commented 2 years ago

@simeonschaub Sure, I'll have a go. Might take another day or two for me to grok all the details and come up with something concise. Wouldn't want to upload something that's misleading.

Preetham-Reddy-007 commented 2 years ago

@adigitoleo thanks for the precise details. I am a beginner and just started with Julia, might take some time.

adigitoleo commented 2 years ago

@Preetham-Reddy-007 these issues might be simpler to start with, they are also about improving documentation: #30469, #28712

MashiatK commented 1 year ago

I want to work on this. If the issue is open, please assign it to me.

gbaraldi commented 1 year ago

@MashiatK Same as with the other issues, just open a PR and we can go from there, assignment isn't really necessary ;)

adigitoleo commented 1 year ago

I just rebased onto master if there is still interest. The linked PR (#43891) was my attemt to clarify some of the documentation on parametric types, but feel free to open a new PR if you have other ideas.