forked from JuliaLang/julia
-
Notifications
You must be signed in to change notification settings - Fork 0
/
types.md
1653 lines (1266 loc) · 64.3 KB
/
types.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# [Types](@id man-types)
Types in Julia establish distinctions between different kinds of data,
and are used by the compiler to infer the intended use of those data.
Programming languages have traditionally employed one of two quite different type systems:
static type systems, where expressions must have a computable type before the execution of the program,
and dynamic type systems, where types are only computed at run time,
when the actual values manipulated by the program are available.
Statically typed languages typically offer faster execution,
at the cost of type annotations which must be explicitly added by the programmer.
Dynamic typing, on the other hand, allows for polymorphism,
which is the ability to write code that can operate on different types.
Only by explicitly checking types, or when objects fail to support operations at run time,
are the types of any values ever restricted.
Julia's type system is dynamic, but gains some of the advantages of static type systems
by allowing optional type annotations. Type annotations are important for defining efficient new data structures,
can help enforce correctness and clarity, and can sometimes improve efficiency
(although in many contexts the Julia compiler infers types automatically.
Type annotations also play a central role in Julia because they control method dispatch:
functions can be extended to new behaviours for different argument types.
Method dispatch is explored in detail in the [Methods](@ref) section, but is rooted in the type system presented here.
All values in a Julia program belong to exactly one concrete type.
All types belong to a single type tree, i.e. they are all at least related to the [`Any`](@ref) type.
User-defined types are treated the same as built-in types.
For additional flexibility, types can be defined as concrete, abstract or parametric.
Their relation to other types must be explicitly declared.
One particularly distinctive feature of Julia's type system is that only abstract types can be used as supertypes for a family of related types.
Concrete types are final, and may not subtype each other.
While this might at first seem unduly restrictive, it has many beneficial consequences with surprisingly
few drawbacks. Both [abstract types](#man-abstract-types) and [parametric types](#Parametric-Types)
can be used to create flexible interfaces while leveraging method dispatch and mitigating type errors.
The methods [`isabstracttype`](@ref) and [`isconcretetype`](@ref) can be used
to check if a type is abstract or concrete.
They may both return `false` if the type is parametrized but not abstract.
The [`subtypes`](@ref) and [`supertypes`](@ref) methods
are also provided for dynamic inspection of the type tree.
The [`typeof`](@ref) and [`isa`](@ref) methods facilitate checking the type of a value.
Julia's type system is designed to be powerful and expressive, yet clear, intuitive and unobtrusive.
Many Julia programmers may never feel the need to write code that explicitly uses types. Some
kinds of programming, however, become clearer, simpler, faster and more robust with declared types.
## Type Annotations
The default behavior in Julia when type annotations are omitted is to allow values to be of any type.
When additional expressiveness is needed, however,
it is easy to gradually introduce explicit annotations into previously "untyped" code.
Adding annotations serves four primary purposes: to take advantage
of Julia's powerful multiple-dispatch mechanism, to improve human readability,
to catch programmer errors, and to assist the compiler (especially when defining new types).
The `::` operator can be used to attach type annotations to expressions and variables in programs.
It is also used for type assertions in method signatures (see [Defining Methods](#Defining-Methods)).
When appended to an expression computing a value, the `::` operator is read as "is an instance
of". It can be used anywhere to assert that the value of the expression on the left is an instance
of the type on the right. When the type on the right is concrete, the value on the left must have
that type as its implementation -- recall that all concrete types are final, so no implementation
is a subtype of any other. When the type is abstract, it suffices for the value to be implemented
by a concrete type that is a subtype of the abstract type. If the type assertion is not true,
an exception is thrown, otherwise, the left-hand value is returned:
```jldoctest
julia> (1+2)::AbstractFloat
ERROR: TypeError: in typeassert, expected AbstractFloat, got a value of type Int64
julia> (1+2)::Int
3
```
This allows a type assertion to be attached to any expression in-place.
When the type on the right is concrete,
the value on the left must have that type as its implementation.
When the type is abstract,
it suffices for the value to have a concrete type that is a subtype of the abstract type.
When appended to a variable on the left-hand side of an assignment, or as part of a `local` declaration,
the `::` operator means something a bit different: it declares the variable to always have the
specified type, like a type declaration in a statically-typed language such as C. Every value
assigned to the variable will be converted to the declared type using [`convert`](@ref):
```jldoctest
julia> function foo()
x::Int8 = 100
x
end
foo (generic function with 1 method)
julia> x = foo()
100
julia> typeof(x)
Int8
```
This feature is useful for avoiding performance "gotchas" that could occur if one of the assignments
to a variable changed its type unexpectedly.
This "declaration" behavior only occurs in specific contexts:
```julia
local x::Int8 # in a local declaration
x::Int8 = 10 # as the left-hand side of an assignment
```
and applies to the whole current scope, even before the declaration.
As of Julia 1.8, type annotations can now be used in global scope i.e.
type annotations can be added to global variables to make accessing them type stable.
```julia
julia> x::Int = 10
10
julia> x = 3.5
ERROR: InexactError: Int64(3.5)
julia> function foo(y)
global x = 15.8 # throws an error when foo is called
return x + y
end
foo (generic function with 1 method)
julia> foo(10)
ERROR: InexactError: Int64(15.8)
```
Declarations can also be attached to function definitions:
```julia
function sinc(x)::Float64
if x == 0
return 1
end
return sin(pi*x)/(pi*x)
end
```
Returning from this function behaves just like an assignment to a variable with a declared type:
the value is always converted to `Float64`.
## [Abstract Types](@id man-abstract-types)
Abstract types cannot be instantiated, and serve only as nodes in the type graph, thereby describing
sets of related concrete types: those concrete types which are their descendants. We begin with
abstract types even though they have no instantiation because they are the backbone of the type
system: they form the conceptual hierarchy which makes Julia's type system more than just a collection
of object implementations.
Recall that in [Integers and Floating-Point Numbers](@ref), we introduced a variety of concrete
types of numeric values: [`Int8`](@ref), [`UInt8`](@ref), [`Int16`](@ref), [`UInt16`](@ref),
[`Int32`](@ref), [`UInt32`](@ref), [`Int64`](@ref), [`UInt64`](@ref), [`Int128`](@ref),
[`UInt128`](@ref), [`Float16`](@ref), [`Float32`](@ref), and [`Float64`](@ref). Although
they have different representation sizes, `Int8`, `Int16`, `Int32`, `Int64` and `Int128`
all have in common that they are signed integer types. Likewise `UInt8`, `UInt16`, `UInt32`,
`UInt64` and `UInt128` are all unsigned integer types, while `Float16`, `Float32` and
`Float64` are distinct in being floating-point types rather than integers. It is common for
a piece of code to make sense, for example, only if its arguments are some kind of integer,
but not really depend on what particular *kind* of integer. For example, the greatest common
denominator algorithm works for all kinds of integers, but will not work for floating-point
numbers. Abstract types allow the construction of a hierarchy of types, providing a context
into which concrete types can fit. This allows you, for example, to easily program to any type
that is an integer, without restricting an algorithm to a specific type of integer.
Abstract types are declared using the [`abstract type`](@ref) keyword. The general syntaxes for declaring an
abstract type are:
```
abstract type «name» end
abstract type «name» <: «supertype» end
```
The `abstract type` keyword introduces a new abstract type, whose name is given by `«name»`. This
name can be optionally followed by [`<:`](@ref) and an already-existing type, indicating that the newly
declared abstract type is a subtype of this "parent" type.
When no supertype is given, the default supertype is `Any` -- a predefined abstract type that
all objects are instances of and all types are subtypes of. In type theory, `Any` is commonly
called "top" because it is at the apex of the type graph. Julia also has a predefined abstract
"bottom" type, at the nadir of the type graph, which is written as `Union{}`. It is the exact
opposite of `Any`: no object is an instance of `Union{}` and all types are supertypes of `Union{}`.
Let's consider some of the abstract types that make up Julia's numerical hierarchy:
```julia
abstract type Number end
abstract type Real <: Number end
abstract type AbstractFloat <: Real end
abstract type Integer <: Real end
abstract type Signed <: Integer end
abstract type Unsigned <: Integer end
```
The [`Number`](@ref) type is a direct child type of `Any`, and [`Real`](@ref) is its child.
In turn, `Real` has two children (it has more, but only two are shown here; we'll get to
the others later): [`Integer`](@ref) and [`AbstractFloat`](@ref), separating the world into
representations of integers and representations of real numbers. Representations of real
numbers include floating-point types, but also include other types, such as rationals.
`AbstractFloat` includes only floating-point representations of real numbers. Integers
are further subdivided into [`Signed`](@ref) and [`Unsigned`](@ref) varieties.
The `<:` operator in general means "is a subtype of", and, used in declarations like those above,
declares the right-hand type to be an immediate supertype of the newly declared type. It can also
be used in expressions as a subtype operator which returns `true` when its left operand is a
subtype of its right operand:
```jldoctest
julia> Integer <: Number
true
julia> Integer <: AbstractFloat
false
```
An important use of abstract types is to provide default implementations for concrete types. To
give a simple example, consider:
```julia
function myplus(x,y)
x+y
end
```
The first thing to note is that the above argument declarations are equivalent to `x::Any` and
`y::Any`. When this function is invoked, say as `myplus(2,5)`, the dispatcher chooses the most
specific method named `myplus` that matches the given arguments. (See [Methods](@ref) for more
information on multiple dispatch.)
Assuming no method more specific than the above is found, Julia next internally defines and compiles
a method called `myplus` specifically for two `Int` arguments based on the generic function given
above, i.e., it implicitly defines and compiles:
```julia
function myplus(x::Int,y::Int)
x+y
end
```
and finally, it invokes this specific method.
Thus, abstract types allow programmers to write generic functions that can later be used as the
default method by many combinations of concrete types. Thanks to multiple dispatch, the programmer
has full control over whether the default or more specific method is used.
An important point to note is that there is no loss in performance if the programmer relies on
a function whose arguments are abstract types, because it is recompiled for each tuple of concrete
argument types with which it is invoked. (There may be a performance issue, however, in the case
of function arguments that are containers of abstract types; see [Performance Tips](@ref man-performance-abstract-container).)
## Primitive Types
!!! warning
It is almost always preferable to wrap an existing primitive type in a new
composite type than to define your own primitive type.
This functionality exists to allow Julia to bootstrap the standard primitive
types that LLVM supports. Once they are defined, there is very little reason
to define more.
A primitive type is a concrete type whose data consists of plain old bits. Classic examples of primitive
types are integers and floating-point values. Unlike most languages, Julia lets you declare your
own primitive types, rather than providing only a fixed set of built-in ones. In fact, the standard
primitive types are all defined in the language itself:
```julia
primitive type Float16 <: AbstractFloat 16 end
primitive type Float32 <: AbstractFloat 32 end
primitive type Float64 <: AbstractFloat 64 end
primitive type Bool <: Integer 8 end
primitive type Char <: AbstractChar 32 end
primitive type Int8 <: Signed 8 end
primitive type UInt8 <: Unsigned 8 end
primitive type Int16 <: Signed 16 end
primitive type UInt16 <: Unsigned 16 end
primitive type Int32 <: Signed 32 end
primitive type UInt32 <: Unsigned 32 end
primitive type Int64 <: Signed 64 end
primitive type UInt64 <: Unsigned 64 end
primitive type Int128 <: Signed 128 end
primitive type UInt128 <: Unsigned 128 end
```
The general syntaxes for declaring a primitive type are:
```
primitive type «name» «bits» end
primitive type «name» <: «supertype» «bits» end
```
The number of bits indicates how much storage the type requires and the name gives the new type
a name. A primitive type can optionally be declared to be a subtype of some supertype. If a supertype
is omitted, then the type defaults to having `Any` as its immediate supertype. The declaration
of [`Bool`](@ref) above therefore means that a boolean value takes eight bits to store, and has
[`Integer`](@ref) as its immediate supertype. Currently, only sizes that are multiples of
8 bits are supported and you are likely to experience LLVM bugs with sizes other than those used above.
Therefore, boolean values, although they really need just a single bit, cannot be declared to be any
smaller than eight bits.
The types [`Bool`](@ref), [`Int8`](@ref) and [`UInt8`](@ref) all have identical representations:
they are eight-bit chunks of memory. Since Julia's type system is nominative, however, they
are not interchangeable despite having identical structure. A fundamental difference between
them is that they have different supertypes: [`Bool`](@ref)'s direct supertype is [`Integer`](@ref),
[`Int8`](@ref)'s is [`Signed`](@ref), and [`UInt8`](@ref)'s is [`Unsigned`](@ref). All other
differences between [`Bool`](@ref), [`Int8`](@ref), and [`UInt8`](@ref) are matters of
behavior -- the way functions are defined to act when given objects of these types as
arguments. This is why a nominative type system is necessary: if structure determined type,
which in turn dictates behavior, then it would be impossible to make [`Bool`](@ref) behave
any differently than [`Int8`](@ref) or [`UInt8`](@ref).
## Composite Types
[Composite types](https://en.wikipedia.org/wiki/Composite_data_type) are called records, structs,
or objects in various languages. A composite type is a collection of named fields,
an instance of which can be treated as a single value. In many languages, composite types are
the only kind of user-definable type, and they are by far the most commonly used user-defined
type in Julia as well.
In mainstream object oriented languages, such as C++, Java, Python and Ruby, composite types also
have named functions associated with them, and the combination is called an "object". In purer
object-oriented languages, such as Ruby or Smalltalk, all values are objects whether they are
composites or not. In less pure object oriented languages, including C++ and Java, some values,
such as integers and floating-point values, are not objects, while instances of user-defined composite
types are true objects with associated methods. In Julia, all values are objects, but functions
are not bundled with the objects they operate on. This is necessary since Julia chooses which
method of a function to use by multiple dispatch, meaning that the types of *all* of a function's
arguments are considered when selecting a method, rather than just the first one (see [Methods](@ref)
for more information on methods and dispatch). Thus, it would be inappropriate for functions to
"belong" to only their first argument. Organizing methods into function objects rather than having
named bags of methods "inside" each object ends up being a highly beneficial aspect of the language
design.
Composite types are introduced with the [`struct`](@ref) keyword followed by a block of field names, optionally
annotated with types using the `::` operator:
```jldoctest footype
julia> struct Foo
bar
baz::Int
qux::Float64
end
```
Fields with no type annotation default to `Any`, and can accordingly hold any type of value.
New objects of type `Foo` are created by applying the `Foo` type object like a function
to values for its fields:
```jldoctest footype
julia> foo = Foo("Hello, world.", 23, 1.5)
Foo("Hello, world.", 23, 1.5)
julia> typeof(foo)
Foo
```
When a type is applied like a function it is called a *constructor*. Two constructors are generated
automatically (these are called *default constructors*). One accepts any arguments and calls
[`convert`](@ref) to convert them to the types of the fields, and the other accepts arguments
that match the field types exactly. The reason both of these are generated is that this makes
it easier to add new definitions without inadvertently replacing a default constructor.
Since the `bar` field is unconstrained in type, any value will do. However, the value for `baz`
must be convertible to `Int`:
```jldoctest footype
julia> Foo((), 23.5, 1)
ERROR: InexactError: Int64(23.5)
Stacktrace:
[...]
```
You may find a list of field names using the [`fieldnames`](@ref) function.
```jldoctest footype
julia> fieldnames(Foo)
(:bar, :baz, :qux)
```
You can access the field values of a composite object using the traditional `foo.bar` notation:
```jldoctest footype
julia> foo.bar
"Hello, world."
julia> foo.baz
23
julia> foo.qux
1.5
```
Composite objects declared with `struct` are *immutable*; they cannot be modified
after construction. This may seem odd at first, but it has several advantages:
* It can be more efficient. Some structs can be packed efficiently into arrays, and
in some cases the compiler is able to avoid allocating immutable objects entirely.
* It is not possible to violate the invariants provided by the type's constructors.
* Code using immutable objects can be easier to reason about.
An immutable object might contain mutable objects, such as arrays, as fields. Those contained
objects will remain mutable; only the fields of the immutable object itself cannot be changed
to point to different objects.
Where required, mutable composite objects can be declared with the keyword [`mutable struct`](@ref), to be
discussed in the next section.
If all the fields of an immutable structure are indistinguishable (`===`) then two immutable values containing those fields are also indistinguishable:
```jldoctest
julia> struct X
a::Int
b::Float64
end
julia> X(1, 2) === X(1, 2)
true
```
There is much more to say about how instances of composite types are created, but that discussion
depends on both [Parametric Types](@ref) and on [Methods](@ref), and is sufficiently important
to be addressed in its own section: [Constructors](@ref man-constructors).
For many user-defined types `X`, you may want to define a method [`Base.broadcastable(x::X) = Ref(x)`](@ref man-interfaces-broadcasting)
so that instances of that type act as 0-dimensional "scalars" for [broadcasting](@ref Broadcasting).
## Mutable Composite Types
If a composite type is declared with `mutable struct` instead of `struct`, then instances of
it can be modified:
```jldoctest bartype
julia> mutable struct Bar
baz
qux::Float64
end
julia> bar = Bar("Hello", 1.5);
julia> bar.qux = 2.0
2.0
julia> bar.baz = 1//2
1//2
```
An extra interface between the fields and the user can be provided through [Instance Properties](@ref man-instance-properties).
This grants more control on what can be accessed and modified using the `bar.baz` notation.
In order to support mutation, such objects are generally allocated on the heap, and have
stable memory addresses.
A mutable object is like a little container that might hold different values over time,
and so can only be reliably identified with its address.
In contrast, an instance of an immutable type is associated with specific field values ---
the field values alone tell you everything about the object.
In deciding whether to make a type mutable, ask whether two instances
with the same field values would be considered identical, or if they might need to change independently
over time. If they would be considered identical, the type should probably be immutable.
To recap, two essential properties define immutability in Julia:
* It is not permitted to modify the value of an immutable type.
* For bits types this means that the bit pattern of a value once set will never change
and that value is the identity of a bits type.
* For composite types, this means that the identity of the values of its fields will
never change. When the fields are bits types, that means their bits will never change,
for fields whose values are mutable types like arrays, that means the fields will
always refer to the same mutable value even though that mutable value's content may
itself be modified.
* An object with an immutable type may be copied freely by the compiler since its
immutability makes it impossible to programmatically distinguish between the original
object and a copy.
* In particular, this means that small enough immutable values like integers and floats
are typically passed to functions in registers (or stack allocated).
* Mutable values, on the other hand are heap-allocated and passed to
functions as pointers to heap-allocated values except in cases where the compiler
is sure that there's no way to tell that this is not what is happening.
In cases where one or more fields of an otherwise mutable struct is known to be immutable,
one can declare these fields as such using `const` as shown below. This enables some,
but not all of the optimizations of immutable structs, and can be used to enforce invariants
on the particular fields marked as `const`.
!!! compat "Julia 1.8"
`const` annotating fields of mutable structs requires at least Julia 1.8.
```jldoctest baztype
julia> mutable struct Baz
a::Int
const b::Float64
end
julia> baz = Baz(1, 1.5);
julia> baz.a = 2
2
julia> baz.b = 2.0
ERROR: setfield!: const field .b of type Baz cannot be changed
[...]
```
## [Declared Types](@id man-declared-types)
The three kinds of types (abstract, primitive, composite) discussed in the previous
sections are actually all closely related. They share the same key properties:
* They are explicitly declared.
* They have names.
* They have explicitly declared supertypes.
* They may have parameters.
Because of these shared properties, these types are internally represented as instances of the
same concept, `DataType`, which is the type of any of these types:
```jldoctest
julia> typeof(Real)
DataType
julia> typeof(Int)
DataType
```
A `DataType` may be abstract or concrete. If it is concrete, it has a specified size, storage
layout, and (optionally) field names. Thus a primitive type is a `DataType` with nonzero size, but
no field names. A composite type is a `DataType` that has field names or is empty (zero size).
Every concrete value in the system is an instance of some `DataType`.
## Type Unions
A type union is a special abstract type which includes as objects all instances of any of its
argument types, constructed using the special [`Union`](@ref) keyword:
```jldoctest
julia> IntOrString = Union{Int,AbstractString}
Union{Int64, AbstractString}
julia> 1 :: IntOrString
1
julia> "Hello!" :: IntOrString
"Hello!"
julia> 1.0 :: IntOrString
ERROR: TypeError: in typeassert, expected Union{Int64, AbstractString}, got a value of type Float64
```
The compilers for many languages have an internal union construct for reasoning about types; Julia
simply exposes it to the programmer. The Julia compiler is able to generate efficient code in the
presence of `Union` types with a small number of types [^1], by generating specialized code
in separate branches for each possible type.
A particularly useful case of a `Union` type is `Union{T, Nothing}`, where `T` can be any type and
[`Nothing`](@ref) is the singleton type whose only instance is the object [`nothing`](@ref). This pattern
is the Julia equivalent of [`Nullable`, `Option` or `Maybe`](https://en.wikipedia.org/wiki/Nullable_type)
types in other languages. Declaring a function argument or a field as `Union{T, Nothing}` allows
setting it either to a value of type `T`, or to `nothing` to indicate that there is no value.
See [this FAQ entry](@ref faq-nothing) for more information.
## Parametric Types
An important and powerful feature of Julia's type system is that it is parametric: types can take
parameters, so that type declarations actually introduce a whole family of new types -- one for
each possible combination of parameter values. This is often referred to as "generic programming".
There are two kinds of explicitly declared parametric types.
[Parametric composite types](#man-parametric-composite-types)
declare a family of concrete, [composite types](#Composite-Types),
whereas [parametric abstract types](#Parametric-Abstract-Types)
declare a family of [abstract types](#man-abstract-types).
Other kinds of built-in parametric types such as tuple types are discussed in this section as well.
Parametric types can also be used in [type annotations](#Type-Annotations).
!!! note
Parametric types do not represent a single node in the type graph.
Therefore, [`typeof`](@ref) can never return a parametric type name.
To dynamically verify a (parametric) subtype relation, use [`isa`](@ref) instead.
!!! warning
Type parameters are invariant: even though `Float64 <: Real`,
the relation `Foo{Float64} <: Foo{Real}` does NOT hold for any parametric type `Foo`.
However, `Foo{Float64} <: Foo{<:Real}` is `true`.
This subtle but important point is the reason for the common use of
`Container{<:Element}` syntax in type annotations.
### [Parametric Composite Types](@id man-parametric-composite-types)
!!! note
For any parametric composite type,
both [`isabstracttype`](@ref) and [`isconcretetype`](@ref) will return false.
Type parameters are introduced immediately after the type name, and surrounded by curly braces:
```jldoctest pointtype
julia> struct Point{T}
x::T
y::T
end
```
This declaration defines a new parametric type, `Point{T}`,
holding two "coordinates" of the same type `T`.
The parameter `T` is a placeholder, which will be replaced by a particular type
when an instance of this type is created.
Thus, this single declaration actually declares an unlimited number of types:
`Point{Float64}`, `Point{AbstractString}`, `Point{Int64}`, etc.
Each of these is now a usable concrete type:
```jldoctest pointtype
julia> Point{Float64}
Point{Float64}
julia> Point{AbstractString}
Point{AbstractString}
```
`Point` itself is also a valid type object, containing all instances `Point{Float64}`, `Point{AbstractString}`,
etc. as subtypes:
```jldoctest pointtype
julia> Point{Float64} <: Point
true
julia> Point{AbstractString} <: Point
true
```
Concrete `Point` variants with different values of `T` are never subtypes of each other:
```jldoctest pointtype
julia> Point{Float64} <: Point{Int64}
false
julia> Point{Float64} <: Point{Real}
false
```
In other words, in the parlance of type theory, Julia's type parameters are *invariant*.
This is for practical reasons: while any instance of `Point{Float64}` may conceptually be
like an instance of `Point{Real}` as well, the two types have different representations in memory:
* An instance of `Point{Float64}` can be represented compactly and efficiently as an immediate pair
of 64-bit values;
* An instance of `Point{Real}` must be able to hold any pair of instances of [`Real`](@ref).
Since objects that are instances of `Real` can be of arbitrary size and structure, in
practice an instance of `Point{Real}` must be represented as a pair of pointers to
individually allocated `Real` objects.
The efficiency gained by imposing this restriction is magnified enormously in the case of arrays:
an `Array{Float64}` can be stored as a contiguous memory block of 64-bit floating-point values,
whereas an `Array{Real}` must be an array of pointers
to individually allocated [`Real`](@ref) objects -- which may well be
[boxed](https://en.wikipedia.org/wiki/Object_type_%28object-oriented_programming%29#Boxing)
64-bit floating-point values, but also might be arbitrarily large, complex objects, which are
declared to be implementations of the `Real` abstract type.
Since `Point{Float64}` is not a subtype of `Point{Real}`, the following method can't be applied
to arguments of type `Point{Float64}`:
```julia
function norm(p::Point{Real})
sqrt(p.x^2 + p.y^2)
end
```
A correct way to define a method that accepts all arguments of type `Point{T}` where `T` is
a subtype of [`Real`](@ref) is:
```julia
function norm(p::Point{<:Real})
sqrt(p.x^2 + p.y^2)
end
```
(Equivalently, one could define `function norm(p::Point{T} where T<:Real)` or
`function norm(p::Point{T}) where T<:Real`; see [UnionAll Types](@ref).)
More examples will be discussed later in [Methods](@ref).
How does one construct a `Point` object? In the absence of any special
constructor declarations, there are two default ways of creating new composite objects, one in
which the type parameters are explicitly given and the other in which they are implied by the
arguments to the object constructor.
Since the type `Point{Float64}` is a concrete type equivalent to `Point` declared with [`Float64`](@ref)
in place of `T`, it can be applied as a constructor accordingly:
```jldoctest pointtype
julia> p = Point{Float64}(1.0, 2.0)
Point{Float64}(1.0, 2.0)
julia> typeof(p)
Point{Float64}
```
For the default constructor, exactly one argument must be supplied for each field:
```jldoctest pointtype
julia> Point{Float64}(1.0)
ERROR: MethodError: no method matching Point{Float64}(::Float64)
The type `Point{Float64}` exists, but no method is defined for this combination of argument types when trying to construct it.
[...]
julia> Point{Float64}(1.0, 2.0, 3.0)
ERROR: MethodError: no method matching Point{Float64}(::Float64, ::Float64, ::Float64)
The type `Point{Float64}` exists, but no method is defined for this combination of argument types when trying to construct it.
[...]
```
Only one default constructor is generated for parametric types, since overriding it is not possible.
This constructor accepts any arguments and converts them to the field types.
In many cases, it is redundant to provide the type of `Point` object one wants to construct, since
the types of arguments to the constructor call already implicitly provide type information. For
that reason, you can also apply `Point` itself as a constructor, provided that the implied value
of the parameter type `T` is unambiguous:
```jldoctest pointtype
julia> p1 = Point(1.0,2.0)
Point{Float64}(1.0, 2.0)
julia> typeof(p1)
Point{Float64}
julia> p2 = Point(1,2)
Point{Int64}(1, 2)
julia> typeof(p2)
Point{Int64}
```
In the case of `Point`, the type of `T` is unambiguously implied if and only if the two arguments
to `Point` have the same type. When this isn't the case, the constructor will fail with a [`MethodError`](@ref):
```jldoctest pointtype
julia> Point(1,2.5)
ERROR: MethodError: no method matching Point(::Int64, ::Float64)
The type `Point` exists, but no method is defined for this combination of argument types when trying to construct it.
Closest candidates are:
Point(::T, !Matched::T) where T
@ Main none:2
Stacktrace:
[...]
```
For that to work, we could change our definition of the `Point` type:
```jldoctest
julia> struct Point{T1,T2}
x::T1
y::T2
end
julia> p = Point(1, 2.5)
Point{Int64, Float64}(1, 2.5)
julia> p2 = Point(1, 2)
Point{Int64, Int64}(1, 2)
```
Constructor methods to appropriately handle more complex cases can also be defined,
see the [Constructors](@ref man-constructors) section.
### Parametric Abstract Types
Parametric abstract type declarations declare a collection of abstract types, in much the same
way:
```jldoctest pointytype
julia> abstract type Pointy{T} end
```
With this declaration, `Pointy{T}` is a distinct abstract type for each type or integer value
of `T`. As with parametric composite types, each such instance is a subtype of `Pointy`:
```jldoctest pointytype
julia> Pointy{Int64} <: Pointy
true
julia> Pointy{1} <: Pointy
true
```
Parametric abstract types are invariant, much as parametric composite types are:
```jldoctest pointytype
julia> Pointy{Float64} <: Pointy{Real}
false
julia> Pointy{Real} <: Pointy{Float64}
false
```
The notation `Pointy{<:Real}` can be used to express the Julia analogue of a
*covariant* type, while `Pointy{>:Int}` the analogue of a *contravariant* type,
but technically these represent *sets* of types (see [UnionAll Types](@ref)).
```jldoctest pointytype
julia> Pointy{Float64} <: Pointy{<:Real}
true
julia> Pointy{Real} <: Pointy{>:Int}
true
```
Much as plain old abstract types serve to create a useful hierarchy of types over concrete types,
parametric abstract types serve the same purpose with respect to parametric composite types. We
could, for example, have declared `Point{T}` to be a subtype of `Pointy{T}` as follows:
```jldoctest pointytype
julia> struct Point{T} <: Pointy{T}
x::T
y::T
end
```
Given such a declaration, for each choice of `T`, we have `Point{T}` as a subtype of `Pointy{T}`:
```jldoctest pointytype
julia> Point{Float64} <: Pointy{Float64}
true
julia> Point{Real} <: Pointy{Real}
true
julia> Point{AbstractString} <: Pointy{AbstractString}
true
```
This relationship is also invariant:
```jldoctest pointytype
julia> Point{Float64} <: Pointy{Real}
false
julia> Point{Float64} <: Pointy{<:Real}
true
```
What purpose do parametric abstract types like `Pointy` serve? Consider if we create a point-like
implementation that only requires a single coordinate because the point is on the diagonal line
*x = y*:
```jldoctest pointytype
julia> struct DiagPoint{T} <: Pointy{T}
x::T
end
```
Now both `Point{Float64}` and `DiagPoint{Float64}` are implementations of the `Pointy{Float64}`
abstraction, and similarly for every other possible choice of type `T`. This allows programming
to a common interface shared by all `Pointy` objects, implemented for both `Point` and `DiagPoint`.
This cannot be fully demonstrated, however, until we have introduced methods and dispatch in the
next section, [Methods](@ref).
There are situations where it may not make sense for type parameters to range freely over all
possible types. In such situations, one can constrain the range of `T` like so:
```jldoctest realpointytype
julia> abstract type Pointy{T<:Real} end
```
With such a declaration, it is acceptable to use any type that is a subtype of
[`Real`](@ref) in place of `T`, but not types that are not subtypes of `Real`:
```jldoctest realpointytype
julia> Pointy{Float64}
Pointy{Float64}
julia> Pointy{Real}
Pointy{Real}
julia> Pointy{AbstractString}
ERROR: TypeError: in Pointy, in T, expected T<:Real, got Type{AbstractString}
julia> Pointy{1}
ERROR: TypeError: in Pointy, in T, expected T<:Real, got a value of type Int64
```
Type parameters for parametric composite types can be restricted in the same manner:
```julia
struct Point{T<:Real} <: Pointy{T}
x::T
y::T
end
```
To give a real-world example of how all this parametric type machinery can be useful, here is
the actual definition of Julia's [`Rational`](@ref) immutable type (except that we omit the
constructor here for simplicity), representing an exact ratio of integers:
```julia
struct Rational{T<:Integer} <: Real
num::T
den::T
end
```
It only makes sense to take ratios of integer values, so the parameter type `T` is restricted
to being a subtype of [`Integer`](@ref), and a ratio of integers represents a value on the
real number line, so any [`Rational`](@ref) is an instance of the [`Real`](@ref) abstraction.
### Tuple Types
Tuples are an abstraction of the arguments of a function -- without the function itself. The salient
aspects of a function's arguments are their order and their types. Therefore a tuple type is similar
to a parameterized immutable type where each parameter is the type of one field. For example,
a 2-element tuple type resembles the following immutable type:
```julia
struct Tuple2{A,B}
a::A
b::B
end
```
However, there are three key differences:
* Tuple types may have any number of parameters.
* Tuple types are *covariant* in their parameters: `Tuple{Int}` is a subtype of `Tuple{Any}`. Therefore
`Tuple{Any}` is considered an abstract type, and tuple types are only concrete if their parameters
are.
* Tuples do not have field names; fields are only accessed by index.
Tuple values are written with parentheses and commas. When a tuple is constructed, an appropriate
tuple type is generated on demand:
```jldoctest
julia> typeof((1,"foo",2.5))
Tuple{Int64, String, Float64}
```
Note the implications of covariance:
```jldoctest
julia> Tuple{Int,AbstractString} <: Tuple{Real,Any}
true
julia> Tuple{Int,AbstractString} <: Tuple{Real,Real}
false
julia> Tuple{Int,AbstractString} <: Tuple{Real,}
false
```
Intuitively, this corresponds to the type of a function's arguments being a subtype of the function's
signature (when the signature matches).
### Vararg Tuple Types
The last parameter of a tuple type can be the special value [`Vararg`](@ref), which denotes any number
of trailing elements:
```jldoctest
julia> mytupletype = Tuple{AbstractString,Vararg{Int}}
Tuple{AbstractString, Vararg{Int64}}
julia> isa(("1",), mytupletype)
true
julia> isa(("1",1), mytupletype)
true
julia> isa(("1",1,2), mytupletype)
true
julia> isa(("1",1,2,3.0), mytupletype)
false
```
Moreover `Vararg{T}` corresponds to zero or more elements of type `T`. Vararg tuple types are
used to represent the arguments accepted by varargs methods (see [Varargs Functions](@ref)).
The special value `Vararg{T,N}` (when used as the last parameter of a tuple type)
corresponds to exactly `N` elements of type `T`. `NTuple{N,T}` is a convenient
alias for `Tuple{Vararg{T,N}}`, i.e. a tuple type containing exactly `N` elements of type `T`.
### Named Tuple Types
Named tuples are instances of the [`NamedTuple`](@ref) type, which has two parameters: a tuple of
symbols giving the field names, and a tuple type giving the field types.
For convenience, `NamedTuple` types are printed using the [`@NamedTuple`](@ref) macro which provides a
convenient `struct`-like syntax for declaring these types via `key::Type` declarations,
where an omitted `::Type` corresponds to `::Any`.
```jldoctest
julia> typeof((a=1,b="hello")) # prints in macro form
@NamedTuple{a::Int64, b::String}
julia> NamedTuple{(:a, :b), Tuple{Int64, String}} # long form of the type
@NamedTuple{a::Int64, b::String}
```
The `begin ... end` form of the `@NamedTuple` macro allows the declarations to be
split across multiple lines (similar to a struct declaration), but is otherwise equivalent: