Skip to content

Commit b37aae8

Browse files
author
Colin Leach
committed
draft Types concept
1 parent 50c3513 commit b37aae8

File tree

5 files changed

+536
-0
lines changed

5 files changed

+536
-0
lines changed

concepts/types/.meta/config.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"authors": [
3+
"colinleach"
4+
],
5+
"contributors": [],
6+
"blurb": "Julia is not object-oriented and has no object hierarchy. Instead, it hs a type hierarchy which is central to how the language is used."
7+
}

concepts/types/about.md

+268
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
# About
2+
3+
So far, the syllabus has not said much about types, but clearly they exist in Julia:
4+
5+
```julia-repl
6+
julia> vals = (42, 4.3, π, "hello", 'Q')
7+
(42, 4.3, π, "hello", 'Q')
8+
9+
julia> typeof(vals)
10+
Tuple{Int64, Float64, Irrational{:π}, String, Char}
11+
```
12+
13+
We never specified the types, but Julia assigned them regardless.
14+
15+
1. Julia has [`types`][types], which are central to its design.
16+
2. Julia can usually "guess" the type, using [`Type Inference`][inference].
17+
18+
The JIT compiler will look through (all) the code, see how a variable is used, and _infer_ a suitable default type compatible with that usage.
19+
20+
## [Type assignment][type-assignment]
21+
22+
Relying on type inference is fine for solving simple tutorial exercises, but for bigger programs you are likely to need more precise control.
23+
24+
On most modern processors, an integer defaults to `Int64`.
25+
We saw in the [`Numbers`][numbers] Concept that a value can be converted to a particular non-default type.
26+
27+
```julia-repl
28+
julia> x = Int16(42)
29+
42
30+
31+
julia> typeof(x)
32+
Int16
33+
```
34+
35+
However, the variable `x` can still be reassigned to a different type:
36+
37+
```julia-repl
38+
julia> x = "changed"
39+
"changed"
40+
41+
julia> typeof(x)
42+
String
43+
```
44+
45+
This is `type instability`, which is:
46+
47+
- Very convenient in small scripts.
48+
- Bad for performance and reliability in bigger programs.
49+
50+
Instead, we can set the type of `x` by using the `::` operator:
51+
52+
```julia-repl
53+
julia> y::Int16 = 42
54+
42
55+
56+
julia> typeof(y)
57+
Int16
58+
59+
julia> y = "changed"
60+
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int16
61+
The function `convert` exists, but no method is defined for this combination of argument types.
62+
```
63+
64+
Now `y` is, and always will be, of type `Int16`.
65+
Thus, the compiler knows how many bytes to reserve for it, and can optimize the rest of the code to rely on a stable type.
66+
In this, the variable is more or less similar to those in a statically-typed language such as C.
67+
68+
## [Type Assertion][type-assignment]
69+
70+
Type assignment, described above, is used on the left-hand side of a variable assignment to constrain the type of that variable.
71+
72+
Using the `::` operator with a value, or something that evaluates to a value, is generally an _assertion_ that the value must be of this type, otherwise an error should be thrown.
73+
74+
```julia-repl
75+
julia> 42::Number
76+
42
77+
78+
julia> "two"::Number
79+
ERROR: TypeError: in typeassert, expected Number, got a value of type String
80+
```
81+
82+
Commonly, this might be used with the return value of a function, as a simple last check that the function behaved as expected.
83+
84+
Note that there is an [`@assert`][assert] macro for other forms of assertion.
85+
86+
## The Type Hierarchy
87+
88+
`int64`, `Int16`, `String`, `Char`: where do these types "come from".
89+
90+
In many object-oriented (OO) languages, each type is a class, subclassing arranges them in a class hierarchy, and class methods define the behaviors.
91+
92+
Java and Ruby are obvious examples of this pattern, but even Python is similar internally.
93+
94+
***Julia has no classes.***
95+
96+
The documented reason is that OO features interfere with the JIT compiler and hurt runtime performance.
97+
98+
Who knows, perhaps they also read this quote:
99+
100+
"Object-oriented programming is an exceptionally bad idea which could only have originated in California."
101+
102+
It is attributed to [Edsger Dijkstra][dijkstra], a brilliant computer scientist for several decades from the 1950's (though not generally noted for his sunny optimism or subtle diplomacy).
103+
104+
And yet, look at this code:
105+
106+
```julia-repl
107+
julia> y::Int16 = 42
108+
42
109+
110+
julia> typeof(y)
111+
Int16
112+
113+
julia> supertype(Int16)
114+
Signed
115+
116+
julia> supertypes(Int16)
117+
(Int16, Signed, Integer, Real, Number, Any)
118+
```
119+
120+
Filling in some details:
121+
122+
- `Int16` is a type, and we can create variables of this type.
123+
- `Int16` is a subtype of `Signed`, and the `supertype()` function shows us this.
124+
- There is a hierarchy of types, going up through `Integer`, `Real` and `Number` to `Any` at the top, and `subtypes()` will list this branch of the hierarchy for us.
125+
126+
All branches end in `Any`, which is unique in being its own supertype.
127+
128+
```julia-repl
129+
julia> supertypes(String)
130+
(String, AbstractString, Any)
131+
132+
julia> supertype(Any)
133+
Any
134+
```
135+
136+
So, Julia has no `class` hierarchy, but it _does_ have a `type` hierarchy.
137+
Trying to show the entire hierarchy give a [huge tree][hierarchy-diagram], impossible to really view.
138+
Looking at parts of it is something [discussed online][hierarchy-diagram].
139+
140+
Eventually, we will try to unpick how this works, but there is much more to explore first.
141+
142+
## Testing for Types
143+
144+
We can use [`typeof()`][typeof] to test for equality in the usual way.
145+
146+
```julia-repl
147+
julia> typeof(11)
148+
Int64
149+
150+
julia> typeof(11) == Int64
151+
true
152+
153+
julia> typeof(11) == Number
154+
false
155+
```
156+
157+
The type equality must be exact, as this form of comparison has no understanding of the type hierarchy.
158+
159+
More flexibly, [`isa`][isa] will tell us if a value has either the same type as a comparator, or a subtype of it.
160+
It can be used in either function or infix form.
161+
162+
```julia-repl
163+
julia> 12 isa Int64
164+
true
165+
166+
julia> 12 isa Number
167+
true
168+
169+
julia> isa(12, Number)
170+
true
171+
172+
julia> 12 isa String
173+
false
174+
```
175+
176+
Note that `isa` expects a `value` on the left, not a `type`.
177+
178+
Trying to compare two _types_ this way will give unexpected results.
179+
The correct operator is `<:`, which we will see a lot more of in future concepts.
180+
181+
```julia-repl
182+
julia> Int64 isa Number ## Don't do this!
183+
false
184+
185+
julia> Int64 <: Number
186+
true
187+
```
188+
189+
## Abstract versus Concrete Types
190+
191+
We saw that the type hierarchy forms a tree structure (in the Computer Science sense, with the root at the top).
192+
193+
Each item in the tree is a `node`, and these can be divided into categories:
194+
195+
1. Nodes with subtypes are called [`abstract`][abstract].
196+
2. Leaf nodes, with no subtypes, are called [`concrete`][concrete].
197+
198+
```julia-repl
199+
julia> subtypes(Integer) # an abstract type
200+
3-element Vector{Any}:
201+
Bool
202+
Signed
203+
Unsigned
204+
205+
julia> subtypes(Int64) # a concrete type
206+
Type[]
207+
```
208+
209+
This is an important distinction, because only concrete types can be `instantiated` as variables.
210+
211+
212+
```julia-repl
213+
julia> a::Int16 = 42
214+
42
215+
216+
julia> typeof(a)
217+
Int16
218+
219+
julia> b::Integer = 42
220+
42
221+
222+
julia> typeof(b)
223+
Int64
224+
```
225+
226+
Note that trying to use an abstract type gives no error message (in this case), but the compiler creates an appropriate concrete type: `Int64` instead of `Integer`.
227+
At least it prevents the variable being assigned to a non-integer:
228+
229+
```julia-repl
230+
julia> f::Integer = "hello"
231+
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Integer
232+
The function `convert` exists, but no method is defined for this combination of argument types.
233+
```
234+
235+
Type assignment with an abstract type is thus a _constraint_ on the variable type, to any subtype of (in the above case) `Integer`.
236+
This is something unusual in the world of programming languages: weaker than a type assignment in C, stronger than a type hint in recent versions of Python.
237+
238+
The functions `isabstracttype()` and `isconcretetype()` allow testing.
239+
Note that these are not just negations of one another: we will see in a later Concept that some types can be neither abstract nor concrete.
240+
241+
242+
```julia-repl
243+
julia> isconcretetype(Integer), isabstracttype(Integer)
244+
(false, true)
245+
246+
julia> isconcretetype(Int64), isabstracttype(Int64)
247+
(true, false)
248+
249+
# Vector is neither
250+
julia> isconcretetype(Vector), isabstracttype(Vector)
251+
(false, false)
252+
```
253+
254+
255+
256+
257+
[dijkstra]: https://en.wikipedia.org/wiki/Edsger_W._Dijkstra
258+
[hierarchy-diagram]: https://discourse.julialang.org/t/diagram-with-all-julia-types/5018
259+
[primitive]: https://docs.julialang.org/en/v1/manual/types/#Primitive-Types
260+
[types]: "https://docs.julialang.org/en/v1/manual/types/"
261+
[inference]: https://en.wikipedia.org/wiki/Type_inference
262+
[numbers]: https://exercism.org/tracks/julia/concepts/numbers
263+
[assert]: https://docs.julialang.org/en/v1/base/base/#Base.@assert
264+
[type-assignment]: https://docs.julialang.org/en/v1/manual/types/#Type-Declarations
265+
[typeof]: https://docs.julialang.org/en/v1/base/base/#Core.typeof
266+
[isa]: https://docs.julialang.org/en/v1/base/base/#Core.isa
267+
[abstract]: https://docs.julialang.org/en/v1/base/base/#abstract%20type
268+
[concrete]: https://docs.julialang.org/en/v1/manual/types/#Type-Declarations

0 commit comments

Comments
 (0)