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