Skip to content

Commit efe9719

Browse files
committed
Moved to a more type-along approach and added some exercies
1 parent c078c0e commit efe9719

File tree

9 files changed

+447
-94
lines changed

9 files changed

+447
-94
lines changed

_config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ title: "Object Oriented Programming with Fortran"
2222
# Life cycle stage of the lesson
2323
# See this page for more details: https://cdh.carpentries.org/the-lesson-life-cycle.html
2424
# Possible values: "pre-alpha", "alpha", "beta", "stable"
25-
life_cycle: "pre-alpha"
25+
life_cycle: "stable"
2626

2727
#------------------------------------------------------------
2828
# Generic settings (should not need to change).

_episodes/derived_types.md

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
---
22
title: "Derived Types"
3-
teaching: 15
4-
exercises: 0
3+
teaching: 10
4+
exercises: 5
55
questions:
66
- "What is a derived type?"
77
- "How do you use derived types?"
88
- "Can access to individual components of a derive type be controlled?"
99
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."
1212
keypoints:
1313
- "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."
1515
---
1616

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

1919
To create a new derived type you use the following format.
2020
~~~
@@ -34,7 +34,7 @@ Finally individual elements or members of a derived type variable, or **object**
3434
my_variable%member1
3535
~~~
3636
{: .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.
3838

3939
A full example of creating a new derived type, `t_vector`, which holds an array of `real` values is shown below.
4040

@@ -82,6 +82,15 @@ $ ./derived_types
8282

8383
### Creating new objects
8484
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`.
85+
86+
Lets add those functions now.
87+
88+
~~~
89+
$ cp derived_types.f90 derived_types_init.f90
90+
$ nano derived_types_init.f90
91+
~~~
92+
{: .bash}
93+
8594
<div class="gitfile" markdown="1">
8695
<div class="language-plaintext fortran highlighter-rouge">
8796
<div class="highlight">
@@ -128,22 +137,71 @@ end program
128137
[derived_types_init.f90](https://github.com/acenet-arc/fortran_oop_as_a_second_language/blob/gh-pages/code/derived_types_init.f90)
129138
</div>
130139

131-
### Access modifiers
132-
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.
133141

142+
Lets compile and run.
134143
~~~
135-
module m_vector
136-
implicit none
137-
138-
type t_vector
139-
integer,private:: num_elements
140-
real,dimension(:),allocatable:: elements
141-
end type
142-
...
143-
end module
144+
$ gfortran ./derived_types_init.f90 -o derived_types_init
145+
$ ./derived_types_init
144146
~~~
145-
{: .fortran}
146-
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.
201+
> > </li>
202+
> > </ol>
203+
> {: .solution}
204+
{: .challenge}
147205
148206
{% include links.md %}
149207

_episodes/destructors.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ exercises: 0
55
questions:
66
- "What is a destructor?"
77
objectives:
8-
- "How do you create a destructor."
8+
- "Create a destructor for our two derived types to deallocate memory."
99
keypoints:
1010
- "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."
1112
---
1213
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.
1314

@@ -22,8 +23,13 @@ end type
2223

2324
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.
2425

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

28+
~~~
29+
$ cp type_bound_procedures_select_type.f90 destructor.f90
30+
$ nano destructor.f90
31+
~~~
32+
{: .bash}
2733
<div class="gitfile" markdown="1">
2834
<div class="language-plaintext fortran highlighter-rouge">
2935
<div class="highlight">
@@ -119,5 +125,29 @@ end program
119125
[destructor.f90](https://github.com/acenet-arc/fortran_oop_as_a_second_language/blob/gh-pages/code/destructor.f90)
120126
</div>
121127

128+
~~~
129+
$ gfortran destructor.f90 destructor
130+
$ ./destructor
131+
~~~
132+
{: .bash}
133+
~~~
134+
t_vector:
135+
num_elements= 0
136+
elements=
137+
t_vector:
138+
num_elements= 4
139+
elements=
140+
2.00000000
141+
0.00000000
142+
0.00000000
143+
0.00000000
144+
t_vector_3:
145+
num_elements= 3
146+
elements=
147+
1.00000000
148+
0.00000000
149+
0.00000000
150+
~~~
151+
{: .output}
122152
{% include links.md %}
123153

_episodes/extending_types.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ keypoints:
1010
- "Type extension allows you to build upon an existing derived type to create a new derived type."
1111
---
1212

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

1515
To create a new extended derived type has the following format.
1616
~~~
@@ -23,7 +23,13 @@ Here `<parent type name>` is the name of a derived type to be extended, and `<ch
2323

2424
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.
2525

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.
27+
28+
~~~
29+
$ cp derived_types_init.f90 type_extension.f90
30+
$ nano type_extension.f90
31+
~~~
32+
{: .bash}
2733

2834
<div class="gitfile" markdown="1">
2935
<div class="language-plaintext fortran highlighter-rouge">
@@ -84,4 +90,18 @@ end program
8490
[type_extension.f90](https://github.com/acenet-arc/fortran_oop_as_a_second_language/blob/gh-pages/code/type_extension.f90)
8591
</div>
8692

87-
{% include links.md %}
93+
~~~
94+
$ gfortran type_extension.f90 -o type_extension
95+
$ ./type_extension
96+
~~~
97+
{: .bash}
98+
99+
~~~
100+
numbers_none%num_elements= 0
101+
numbers_some%num_elements= 4
102+
numbers_some%elements(1)= 2.00000000
103+
location%elements(1)= 1.00000000
104+
~~~
105+
{: .output}
106+
107+
{% include links.md %}

_episodes/interfaces.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ keypoints:
1212
- "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."
1313
---
1414

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

1717
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.
1818
~~~
@@ -24,9 +24,15 @@ end interface
2424
~~~
2525
{: .fortran}
2626

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

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.
30+
31+
~~~
32+
$ cp type_extension.f90 interface_blocks.f90
33+
$ nano interface_blocks.f90
34+
~~~
35+
{: .bash}
3036

3137
<div class="gitfile" markdown="1">
3238
<div class="language-plaintext fortran highlighter-rouge">
@@ -91,6 +97,19 @@ end program
9197
[interface_blocks.f90](https://github.com/acenet-arc/fortran_oop_as_a_second_language/blob/gh-pages/code/interface_blocks.f90)
9298
</div>
9399

100+
~~~
101+
$ gfortran interface_blocks.f90 interface_blocks
102+
$ ./interface_blocks
103+
~~~
104+
{: .bash}
105+
~~~
106+
numbers_none%num_elements= 0
107+
numbers_some%num_elements= 4
108+
numbers_some%elements(1)= 2.00000000
109+
location%elements(1)= 1.00000000
110+
~~~
111+
{: .output}
112+
94113
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**.
95114

96115
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

Comments
 (0)