Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better introduction to parametric types in the manual #43811

Open
adigitoleo opened this issue Jan 14, 2022 · 12 comments · May be fixed by #43891
Open

Better introduction to parametric types in the manual #43811

adigitoleo opened this issue Jan 14, 2022 · 12 comments · May be fixed by #43891
Labels
docs This change adds or pertains to documentation good first issue Indicates a good issue for first-time contributors to Julia types and dispatch Types, subtyping and method dispatch

Comments

@adigitoleo
Copy link
Contributor

adigitoleo commented Jan 14, 2022

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

  • why both isabstracttype(Foo) and isconcretetype(Foo) return false for the parametric type Foo, i.e. that parametric types are neither abstract nor concrete
  • the difference between the following definitions of Foo
struct Foo{T}
    a::T
    b::T
end

struct Foo{T1, T2}
    a::T1
    b::T2
end
  • why typeof(foo) <: Foo is true, however supertype(typeof(foo)) == Foo is false (it's Any)
  • why Foo isa DataType is false

and (more visibly) inform

  • that parametric types cannot be subtyped they can, parametric abstract types are a thing
  • that reified instances of parametric types are of a concrete type, therefore typeof(foo) != Foo where foo is an instance of Foo
  • that foo isa Foo must be used to test if foo is an instance of Foo

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?)

@fredrikekre fredrikekre added docs This change adds or pertains to documentation good first issue Indicates a good issue for first-time contributors to Julia types and dispatch Types, subtyping and method dispatch labels Jan 14, 2022
@Preetham-Reddy-007
Copy link

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

@adigitoleo
Copy link
Contributor Author

adigitoleo commented Jan 16, 2022

@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 😄, 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> 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
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Member

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
Copy link
Contributor Author

@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
Copy link

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

@adigitoleo
Copy link
Contributor Author

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

adigitoleo added a commit to adigitoleo/julia that referenced this issue Jan 22, 2022
First step towards JuliaLang#43811.

The intro section felt a bit long-winded, I've made some changes there first.
adigitoleo added a commit to adigitoleo/julia that referenced this issue Jan 22, 2022
Closes JuliaLang#43811.

Removed some of the jargon and wikipedia links to make room for
a brief alternative `Point{T1,T2}` demonstration.
Shifted some paragraphs around to reflect their importance,
and changed the wording in some places.
adigitoleo added a commit to adigitoleo/julia that referenced this issue Jan 22, 2022
Closes JuliaLang#43811.

- The intro section felt a bit long-winded, I've made some changes there first.
- Clarify that `typeof` returns the concrete type
- Rename Type Declarations section to Type Annotations,
  avoid confusion with Declaring Types section and distinguish use of "type declaration"
  to mean "declaring new types"
- Removed some of the jargon and wikipedia links to make room for
  a brief alternative `Point{T1,T2}` demonstration.
- Shifted some paragraphs around to reflect their importance,
  and changed the wording in some places.
- Rename type declaration -> annotation in other places (docstrings/comments)
adigitoleo added a commit to adigitoleo/julia that referenced this issue Jan 22, 2022
Closes JuliaLang#43811.

- The intro section felt a bit long-winded, I've made some changes there first.
- Clarify that `typeof` returns the concrete type
- Rename Type Declarations section to Type Annotations,
  avoid confusion with Declaring Types section and distinguish use of "type declaration"
  to mean "declaring new types"
- Removed some of the jargon and wikipedia links to make room for
  a brief alternative `Point{T1,T2}` demonstration.
- Shifted some paragraphs around to reflect their importance,
  and changed the wording in some places.
- Rename type declaration -> annotation in other places (docstrings/comments)
@MashiatK
Copy link

MashiatK commented Feb 8, 2023

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

@gbaraldi
Copy link
Member

gbaraldi commented Feb 8, 2023

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

adigitoleo added a commit to adigitoleo/julia that referenced this issue Feb 9, 2023
Closes JuliaLang#43811.

- The intro section felt a bit long-winded, I've made some changes there first.
- Clarify that `typeof` returns the concrete type
- Rename Type Declarations section to Type Annotations,
  avoid confusion with Declaring Types section and distinguish use of "type declaration"
  to mean "declaring new types"
- Removed some of the jargon and wikipedia links to make room for
  a brief alternative `Point{T1,T2}` demonstration.
- Shifted some paragraphs around to reflect their importance,
  and changed the wording in some places.
- Rename type declaration -> annotation in other places (docstrings/comments)
@adigitoleo
Copy link
Contributor Author

adigitoleo commented Feb 9, 2023

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.

adigitoleo added a commit to adigitoleo/julia that referenced this issue Feb 15, 2024
Closes JuliaLang#43811.

- The intro section felt a bit long-winded, I've made some changes there first.
- Clarify that `typeof` returns the concrete type
- Rename Type Declarations section to Type Annotations,
  avoid confusion with Declaring Types section and distinguish use of "type declaration"
  to mean "declaring new types"
- Removed some of the jargon and wikipedia links to make room for
  a brief alternative `Point{T1,T2}` demonstration.
- Shifted some paragraphs around to reflect their importance,
  and changed the wording in some places.
- Rename type declaration -> annotation in other places (docstrings/comments)
adigitoleo added a commit to adigitoleo/julia that referenced this issue Jun 3, 2024
Closes JuliaLang#43811.

- The intro section felt a bit long-winded, I've made some changes there first.
- Clarify that `typeof` returns the concrete type
- Rename Type Declarations section to Type Annotations,
  avoid confusion with Declaring Types section and distinguish use of "type declaration"
  to mean "declaring new types"
- Removed some of the jargon and wikipedia links to make room for
  a brief alternative `Point{T1,T2}` demonstration.
- Shifted some paragraphs around to reflect their importance,
  and changed the wording in some places.
- Rename type declaration -> annotation in other places (docstrings/comments)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs This change adds or pertains to documentation good first issue Indicates a good issue for first-time contributors to Julia types and dispatch Types, subtyping and method dispatch
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants