You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: _episodes/derived_types.md
+78-20Lines changed: 78 additions & 20 deletions
Original file line number
Diff line number
Diff line change
@@ -1,20 +1,20 @@
1
1
---
2
2
title: "Derived Types"
3
-
teaching: 15
4
-
exercises: 0
3
+
teaching: 10
4
+
exercises: 5
5
5
questions:
6
6
- "What is a derived type?"
7
7
- "How do you use derived types?"
8
8
- "Can access to individual components of a derive type be controlled?"
9
9
objectives:
10
-
- "Create a derive type."
11
-
- "Access members of a derive type."
10
+
- "Create a derived type."
11
+
- "Access members of a derived type."
12
12
keypoints:
13
13
- "A derived type allows you to package together a number of basic types that can then be thought of collectively as one new derived type."
14
-
- "Access modifiers can be applied within derived types as well as within modules"
14
+
- "Access modifiers can be applied within derived types as well as within modules to restrict access to members of that derived type."
15
15
---
16
16
17
-
While modules allow you to package variables together in such a way that you can directly refer to those variables, as we saw previously, derived types allow you to package together variables in such a way as to form a new compound variable. With this new compound variable you can refer to it as a group rather than only by the individual components.
17
+
While modules allow you to package variables together in such a way that you can directly refer to those variables, derived types allow you to package together variables in such a way as to form a new compound variable. With this new compound variable you can refer to it as a group rather than only by the individual components.
18
18
19
19
To create a new derived type you use the following format.
20
20
~~~
@@ -34,7 +34,7 @@ Finally individual elements or members of a derived type variable, or **object**
34
34
my_variable%member1
35
35
~~~
36
36
{: .fortran}
37
-
The member variable, `member1`, is declared as part of the derived type.
37
+
The member variable, `member1`, would have been declared as part of the derived type.
38
38
39
39
A full example of creating a new derived type, `t_vector`, which holds an array of `real` values is shown below.
40
40
@@ -82,6 +82,15 @@ $ ./derived_types
82
82
83
83
### Creating new objects
84
84
Creating new vectors is a pretty common thing that we want to do. Lets add some functions to create vectors to reduce the amount of repeated code. Lets create one to make empty vectors, `create_empty_vector` and one to create a vector of a given size allocating the required memory to hold all the elements of the vector, `create_sized_vector`.
Access modifiers can be applied to derived types in a similar way to modules. Here is an example.
140
+
Since the function is declared as a `t_vector` type, members of the returned `t_vector` object can be accessed using the `%` operator from an object with the same name as the function from inside the function. This is similar to how functions normally work, however in this case the function return type is a derived type.
In this case the member variable `num_elements` could no longer be accessed from outside the module, while the `elements` member variable can be.
147
+
{: .bash}
148
+
149
+
~~~
150
+
numbers_none%num_elements= 0
151
+
numbers_some%num_elements= 4
152
+
numbers_some%elements(1)= 2.00000000
153
+
~~~
154
+
{: .output}
155
+
156
+
Now we can use these functions to initialize and allocate memory for our vectors.
157
+
158
+
> ## Deallocating
159
+
> It is a good idea to match allocations to deallocations. We will add this functionality later in the [Destructors episode](../destructors) once we learn a bit more about derived types.
160
+
{: .callout}
161
+
162
+
> ## Access modifiers on derived type modifiers
163
+
> Access modifiers can be applied to derived types in a similar way to modules. Here is an example.
164
+
>
165
+
> ~~~
166
+
> module m_vector
167
+
> implicit none
168
+
>
169
+
> type t_vector
170
+
> integer,private:: num_elements
171
+
> real,dimension(:),allocatable:: elements
172
+
> end type
173
+
> ...
174
+
> end module
175
+
> ~~~
176
+
> {: .fortran}
177
+
> In this case the member variable `num_elements` could no longer be accessed from outside the module, > > while the `elements` member variable can be.
178
+
{: .callout}
179
+
180
+
> ## What is a derived type?
181
+
> Is a derived type:
182
+
> <ol type="a">
183
+
> <li markdown="1">a variable which can have multiple variable members
184
+
> </li>
185
+
> <li markdown="1">an object which can have multiple member variables
186
+
> </li>
187
+
> <li markdown="1">a datatype which can have multiple member variables
188
+
> </li>
189
+
> <li markdown="1">a datatype which can contain only a single value like `integer`,`real`,etc.
190
+
> </li>
191
+
> </ol>
192
+
> > ## Solution
193
+
> > <ol type="a">
194
+
> > <li markdown="1">**NO**: while a derived type can have multiple members, a derived type defines a new datatype where as a variable or object are the instantiation of that derived type or datatype.
195
+
> > </li>
196
+
> > <li markdown="1">**NO**: while an object has a derived type, the object is not the derived type its self.
197
+
> > </li>
198
+
> > <li markdown="1">**Yes**: a derived type is a kind of datatype that can have multiple members.
199
+
> > </li>
200
+
> > <li markdown="1">**No**: derived types can have multiple members, it is possible that they only have one member but they are not restricted to holding a single value.
Copy file name to clipboardExpand all lines: _episodes/destructors.md
+32-2Lines changed: 32 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -5,9 +5,10 @@ exercises: 0
5
5
questions:
6
6
- "What is a destructor?"
7
7
objectives:
8
-
- "How do you create a destructor."
8
+
- "Create a destructor for our two derived types to deallocate memory."
9
9
keypoints:
10
10
- "A destructor is used to perform clean up when an object goes out of scope."
11
+
- "To create a destructor use the **final** keyword when declaring at type bound procedure instead of the procedure keyword."
11
12
---
12
13
One aspect I have been ignoring until now is that the memory we allocate for our vectors is never explicitly freed by our program. So far our program has been simple enough that this is not a serious issue. We have only created a few objects that allocate memory within our main program. When the program execution has completed, that memory is returned to the operating system. However, if we had a long running loop inside our program that created new objects with allocated memory and we never deallocated that memory we would have a problem as our program would steadily increase its memory usage. This is referred to as a memory leak as was mentioned in the first half of this workshop. We can manually deallocate memory as we did with allocating memory before we created a function to create new `t_vector` and `t_vector_3` objects, however there is a way to create a new special type bound procedure that is automatically called with the object goes out of scope to deallocate this memory for us. To do this we use the **final** keyword within the type definition.
13
14
@@ -22,8 +23,13 @@ end type
22
23
23
24
Then as we did for the `display` subroutine, we can create a `<type-finalization-subroutine>` to do anything that needs to be done when an object of this type goes out of scope, including deallocating memory.
24
25
25
-
The below code shows the addition of two subroutines, `destructor_vector`to deallocate memory for `t_vector` objects, and `destructor_vector_3` to deallocate memory for our `t_vector_3` objects.
26
+
Lets add destructors to deallocate our memory for our two derived types.
Copy file name to clipboardExpand all lines: _episodes/extending_types.md
+23-3Lines changed: 23 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ keypoints:
10
10
- "Type extension allows you to build upon an existing derived type to create a new derived type."
11
11
---
12
12
13
-
It is pretty common to use vectors to represent positions in 3D space. Lets create a new derive type which always has only three components. However, it would be really cool if we could reuse our more general vector type to represent one of these specific 3 component vectors. You can do this be using type extension. Type extension allows you to add new members (or not) to an existing type to create a new derived type.
13
+
It is pretty common to use vectors to represent positions in 3D space. Lets create a new derived type which always has only three components. However, it would be really nice if we could reuse our more general vector type to represent one of these specific 3 component vectors. You can do this by using type extension. Type extension allows you to add new members (or not) to an existing type to create a new derived type.
14
14
15
15
To create a new extended derived type has the following format.
16
16
~~~
@@ -23,7 +23,13 @@ Here `<parent type name>` is the name of a derived type to be extended, and `<ch
23
23
24
24
Our new 3 component vector however, doesn't need any new member variables so we don't need to add any new ones. However, by having a distinct derived data type for our 3 component vector will allow us to use specific procedures that work with it as apposed to the those for the more general vector, as we shall see shortly.
25
25
26
-
Below we show how to create our new 3 component vector, `t_vector_3`, as an extension of the original general vector derived type. We have also added a new `create_size_3_vector` function to create new 3 component vectors. The `...` in the below code indicates that the body of the type or procedure has been omitted for brevity and is the same as shown in previous code listings. The file name below the code listing will take you to a web page with the full code listing.
26
+
Lets create a new `t_vector_3` derived type and a `create_size_3_vector` to create new ones.
Copy file name to clipboardExpand all lines: _episodes/interfaces.md
+22-3Lines changed: 22 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,7 @@ keypoints:
12
12
- "Procedures that are part of the same generic interface block must be distinguishable from each other based on the number, order, and type of arguments passed."
13
13
---
14
14
15
-
As mentioned previously it is a very common task to create new objects of a derived type and perform some initialization of the members of that derive type and we had created some functions to do this. One of the functions, `create_empty_vector` takes no arguments and creates a new empty vector, while another procedure, `create_sized_vector` creates a new vector of a specific size passed to the procedure. Both of these functions do the same thing, create a new `t_vector` object and initialize it. One might imagine many different such initialization routines, perhaps ones that take other `t_vectors` or `t_vector_3` objects to use to initialize a new `t_vector` object as a copy of the passed vector. All of these creation, or initialization, functions do basically the same thing but in a slightly different way depending on the arguments passed to them. It starts to get a bit tedious to have to remember all the names of these different initialization functions. If the compiler could somehow distinguish these functions automatically based on the number and type of arguments rather than the procedure name so that we could call the same generic procedure name and it would pick the correct procedure implementation based on the arguments we passed it.
15
+
As mentioned previously it is a very common task to create new objects of a derived type and perform some initialization of the members of that derive type and we had created some functions to do this. One of the functions, `create_empty_vector` takes no arguments and creates a new empty vector, while another procedure, `create_sized_vector` creates a new vector of a specific size passed to the procedure. Both of these functions do the same thing, create a new `t_vector` object and initialize it. One might imagine many different such initialization routines, perhaps ones that take another `t_vectors` or `t_vector_3` objects to use to initialize a new `t_vector` object as a copy of the passed vector. All of these creation, or initialization, functions do basically the same thing but in a slightly different way depending on the arguments passed to them. It starts to get a bit tedious to have to remember all the names of these different initialization functions. If the compiler could somehow distinguish these functions automatically based on the number and type of arguments rather than the procedure name so that we could call the same generic procedure name and it would pick the correct procedure implementation based on the arguments we passed it.
16
16
17
17
It turns out there was a feature added to Fortran 2003, called **interface blocks** which allow multiple procedures to be mapped to one name. The basic syntax of an interface block is as follows.
18
18
~~~
@@ -24,9 +24,15 @@ end interface
24
24
~~~
25
25
{: .fortran}
26
26
27
-
This allows one to call the procedure `<new-procedure-name>` and will be mapped onto different procedure implementations `<existing-procedure-name1>`, `<existing-procedure-name-2>`, etc. based on the type, number, and order of arguments passed to the procedure when calling it. Since the number, type, and order of arguments is the only way for the compiler to know which procedure to call all procedures listed in the interface block must have different types and or number of arguments.
27
+
This allows one to call the procedure `<new-procedure-name>` and will be mapped onto different procedure implementations `<existing-procedure-name1>`, `<existing-procedure-name-2>`, etc. based on the type, number, and order of arguments passed to the procedure when calling it. Since the number, type, and order of arguments is the only way for the compiler to know which procedure to call, all procedures listed in the interface block must have different types and or number of arguments.
28
28
29
-
Lets use interface blocks to group our creation functions for `t_vector` and `t_vector_3` into one procedure name to initialize each derived type. It is common to use the name of the derived type as the name of creation function which returns a new object of that type. Functions defined in this way are referred to as **constructors** as they *construct* new objects of the derived type.
29
+
Lets use interface blocks to group our creation functions for `t_vector` and `t_vector_3` into one procedure name to initialize each of the derived types. It is common to use the name of the derived type as the name of creation function which returns a new object of that type. Functions defined in this way are referred to as **constructors** as they *construct* new objects of the derived type.
The way we have used interface blocks above is what is called a **generic interface** as it maps one generic procedure name we specified to multiple specific procedures. This is very similar to what other object oriented languages call **overloading**.
95
114
96
115
There is another way to use interface blocks without specifying a generic procedure name to map the listed procedures to, referred to as **explicit interfaces**. Explicit interface blocks can be used to define a procedure without actually listing the implementation of it. These are useful when using procedures declared in different compilation units which will be linked into the final program later. This is a bit like a forward declaration or a function prototype in C/C++. If your procedures are declared inside a module, as we have been doing, these explicit interfaces are created for you.
0 commit comments