Skip to content

Commit

Permalink
Various small fixes as I went through it
Browse files Browse the repository at this point in the history
  • Loading branch information
cgeroux committed Apr 18, 2024
1 parent efe9719 commit f47ae45
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 100 deletions.
5 changes: 3 additions & 2 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# an: ACENET
# cc: Compute Canada
# alliance: Digital Research Alliance of Canada
# molmodsim: Compute Canada Molecular Simulation Team
# molmodsim: Compute Canada Molecular Simulation Team
# bst: (compatibility) same as `molmodsim`
carpentry: "an"

Expand Down Expand Up @@ -119,8 +119,9 @@ episode_order:
- derived_types
- extending_types
- interfaces
- break
- type_bound_procedures
- destructors
- operators
- wrapping_up

5 changes: 5 additions & 0 deletions _episodes/break.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
layout: break
title: "Break"
break: 10
---
14 changes: 7 additions & 7 deletions _episodes/derived_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,15 @@ end module
program main
use m_vector
implicit none
type(t_vector) numbers_none,numbers_some
type(t_vector) <div class="codehighlight">numbers_none,numbers_some</div>

numbers_none=<div class="codehighlight">create_empty_vector()</div>
print*, "numbers_none%num_elements=",numbers_none%num_elements
<div class="codehighlight"> numbers_none=create_empty_vector()
print*, "numbers_none%num_elements=",numbers_none%num_elements</div>

numbers_some=<div class="codehighlight">create_sized_vector(4)</div>
<div class="codehighlight"> numbers_some=create_sized_vector(4)
numbers_some%elements(1)=2
print*, "numbers_some%num_elements=",numbers_some%num_elements
print*, "numbers_some%elements(1)=",numbers_some%elements(1)
print*, "numbers_some%elements(1)=",numbers_some%elements(1)</div>

end program
</pre></div></div>
Expand Down Expand Up @@ -159,7 +159,7 @@ Now we can use these functions to initialize and allocate memory for our vectors
> 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.
{: .callout}

> ## Access modifiers on derived type modifiers
> ## Access modifiers and derived types
> Access modifiers can be applied to derived types in a similar way to modules. Here is an example.
>
> ~~~
Expand All @@ -174,7 +174,7 @@ Now we can use these functions to initialize and allocate memory for our vectors
> end module
> ~~~
> {: .fortran}
> In this case the member variable `num_elements` could no longer be accessed from outside the module, > > while the `elements` member variable can be.
> In this case the member variable `num_elements` could no longer be accessed from outside the module, while the `elements` member variable can be.
{: .callout}
> ## What is a derived type?
Expand Down
5 changes: 2 additions & 3 deletions _episodes/destructors.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ keypoints:
- "A destructor is used to perform clean up when an object goes out of scope."
- "To create a destructor use the **final** keyword when declaring at type bound procedure instead of the procedure keyword."
---
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.
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 when the object goes out of scope to deallocate this memory for us. To do this we use the **final** keyword within the type definition.

~~~
type <type-name>
Expand Down Expand Up @@ -126,7 +126,7 @@ end program
</div>

~~~
$ gfortran destructor.f90 destructor
$ gfortran destructor.f90 -o destructor
$ ./destructor
~~~
{: .bash}
Expand All @@ -142,7 +142,6 @@ $ ./destructor
0.00000000
0.00000000
t_vector_3:
num_elements= 3
elements=
1.00000000
0.00000000
Expand Down
8 changes: 5 additions & 3 deletions _episodes/extending_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ teaching: 10
exercises: 0
questions:
- "How do you extend a type?"
- "Why can it be useful to extend a type?"
objectives:
- "Create an extend a type."
- "Create an extended a type."
keypoints:
- "Type extension allows you to build upon an existing derived type to create a new derived type."
- "Type extension allows you to build upon an existing derived type to create a new derived type while adding new functionality or modifying existing functionality."
- "Allows reuse of common code between derived types."
---

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.
Expand Down Expand Up @@ -67,7 +69,7 @@ program main
use m_vector
implicit none
type(t_vector) numbers_none,numbers_some
type(t_vector_3) location
<div class="codehighlight"> type(t_vector_3) location</div>

numbers_none=create_empty_vector()
print*, "numbers_none%num_elements=",numbers_none%num_elements
Expand Down
2 changes: 1 addition & 1 deletion _episodes/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ keypoints:
- "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."
---

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.
As mentioned previously it is a very common task to create new objects of a derived type and perform some initialization of the members. We have already created some functions to do this. One of the functions, `create_empty_vector` takes no arguments and creates a new empty vector, while `create_sized_vector` creates a new vector of a specific size passed to the function. 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.

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.
~~~
Expand Down
2 changes: 1 addition & 1 deletion _episodes/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ $ ./modules
It is possible to control how variables and procedures declared in a module are accessed from outside the module. This can be done either on module wide basis or for specific procedures and variables. If you specify no access modifiers everything will be accessible from outside the module.

There are two access modifiers:
* `private` indicates that that procedure or variable can only be accessed within the module
* `private` indicates that the procedure or variable can only be accessed within the module
* `public` indicates it can be accessed from outside the module.

Below is an example of using the `private` access modifier module wide.
Expand Down
6 changes: 3 additions & 3 deletions _episodes/type_bound_procedures.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ teaching: 10
exercises: 5
questions:
- "What is a type bound procedure?"
- "Can an extended type be passed to a type bound procedure?"
objectives:
- "How do you create a type bound procedure."
keypoints:
Expand Down Expand Up @@ -32,7 +33,7 @@ end program
{: .fortran}
However, in object oriented languages it is common to think of procedures as part of the object or type and have the procedure be called from the object. So instead of calling a procedure and explicitly passing in the object like `call display(vec)` as above the different syntax using the `%` operator `call vec%display()` is more in line with the object oriented way of thinking.

The `%` style of calling a subroutine works exactly the same as the usual way except that the first argument in the subroutine is automatically replaced by the object to the left of the `%` operator. This type of procedure is called a **type bound procedure**. In other languages this might be called a **member function** as it is a member of the type like the component variables are members.
The `%` style of calling a subroutine works exactly the same as the usual way, except that the first argument in the subroutine is automatically replaced by the object to the left of the `%` operator. This type of procedure is called a **type bound procedure**. In other languages this might be called a **member function** as it is a member of the type, like the component variables are members.

To create a type bound procedure you must specify that the type *contains* that procedure. Lets add the `display` type bound procedure now.

Expand Down Expand Up @@ -192,10 +193,10 @@ module m_vector
<div class="codehighlight"> select type (vec)
class is (t_vector)
print*, "t_vector:"
print*, " num_elements=",vec%num_elements
class is (t_vector_3)
print*, "t_vector_3:"
end select</div>
print*, " num_elements=",vec%num_elements
print*, " elements="
do i=1,vec%num_elements
print*, " ",vec%elements(i)
Expand Down Expand Up @@ -262,7 +263,6 @@ $ ./type_bound_procedures_select_type
0.00000000
0.00000000
t_vector_3:
num_elements= 3
elements=
1.00000000
0.00000000
Expand Down
54 changes: 27 additions & 27 deletions code/destructor.f90
Original file line number Diff line number Diff line change
@@ -1,107 +1,107 @@
module m_vector
implicit none

type t_vector
integer:: num_elements
real,dimension(:),allocatable:: elements

contains

procedure:: display
final:: destructor_vector

end type

interface t_vector
procedure:: create_empty_vector
procedure:: create_sized_vector
end interface

type,extends(t_vector):: t_vector_3

contains

final:: destructor_vector_3

end type

interface t_vector_3
procedure:: create_size_3_vector
end interface

contains

subroutine destructor_vector(self)
implicit none
type(t_vector):: self

if (allocated(self%elements)) then
deallocate(self%elements)
endif
end subroutine

subroutine destructor_vector_3(self)
implicit none
type(t_vector_3):: self

if (allocated(self%elements)) then
deallocate(self%elements)
endif
end subroutine

subroutine display(vec)
implicit none
class(t_vector),intent(in):: vec
integer:: i

select type (vec)
class is (t_vector)
print*, "t_vector:"
print*, " num_elements=",vec%num_elements
class is (t_vector_3)
print*, "t_vector_3:"
end select
print*, " num_elements=",vec%num_elements
print*, " elements="
do i=1,vec%num_elements
print*, " ",vec%elements(i)
end do
end subroutine

type(t_vector) function create_empty_vector()
implicit none
create_empty_vector%num_elements=0
end function

type(t_vector) function create_sized_vector(vec_size)
implicit none
integer,intent(in):: vec_size
create_sized_vector%num_elements=vec_size
allocate(create_sized_vector%elements(vec_size))
end function

type(t_vector_3) function create_size_3_vector()
implicit none
create_size_3_vector%num_elements=3
allocate(create_size_3_vector%elements(3))
end function

end module

program main
use m_vector
implicit none
type(t_vector) numbers_none,numbers_some
type(t_vector_3) location

numbers_none=t_vector()
call numbers_none%display()

numbers_some=t_vector(4)
numbers_some%elements(1)=2
call numbers_some%display()

location=t_vector_3()
location%elements(1)=1.0
call location%display()
end program

end program
Loading

0 comments on commit f47ae45

Please sign in to comment.