Immutable declaration

I wonder if it would be useful to have a declaration of “immutable” by which I mean a variable of intrinsic or derived type that can be declared immutable so that once it is first given a value during execution it then behaves as a parameter?

OK, I am not sure “immutable” is quite the right term, and it is longer than I would prefer.

The idea would be that once such a variable had acquired a value once, it could not then be changed without causing a run-time error to be raised.

I find parameters useful but I have plenty of cases where it is inconvenient to have to recompile the program just because I want to change their value from run to run. And my users do not have that option at all.

So once an immutable variable had received its value from a read, from the command line or from an environment variable (to mention just three options) its value was fixed for the remainder of that run.

Of course, it is possible that I have failed to spot a way to emulate this behaviour, but, for instance, ‘protected’ does not quite hit this spot and I am sure this discourse will help me if such functionality is already available.

2 Likes

You can use ASSOCIATE to create an immutable variable within a block:

program main
implicit none
integer :: i
i = 5
associate (j => i**2)
print*,"j=",j
! j = 10 ! invalid
end associate
associate (j => i**3)
print*,"j=",j
end associate
end program main

If the invalid line is reinstated, gfortran says

immutable.f90:7:0:

    7 | j = 10 ! invalid
      | 
Error: 'j' at (1) associated to expression cannot be used in a variable definition context (assignment)

It has been proposed to use let as shorthand for this use of associate.

4 Likes

That is a work around and I was not aware that the associate construct could be used that way to associate a variable with an expression as in your example, I have only ever used associate a bit like a preprocessor to contract a long derived type to something locally more readable.

Thanks and useful.

1 Like

And subject for a tweet? Or have I missed that tweet?

1 Like

Tips are listed by topic here. I mentioned this use of ASSOCIATE on Dec 19.

2 Likes

What also works, is:

x = 1.0
associate ( xx => (x) ) 
    xx = 3.0
end associate

Via the parentheses you create an expression with the value of the variable. And you cannot change the value of an expression.

3 Likes

In reply to @kargl : thanks, but this is one of the cases I really wanted to avoid because the protected variable can be changed any number of times in the module in which is is declared, but, as my marbles deteriorate, more and more protections become necessary.
The associate to expression, constant or otherwise, is a solution, but does not make things quite so explicit, and I will use this.
Thanks to all, I learnt something today.

1 Like

.All the features you asked for are possible using the method suggested by @kargl. One possible expansion would be

module M_immutable
implicit none
private
public guarded, init, single_set
type init
    integer :: i1=10
    integer :: i2=20
    real      :: r1=30.0
    real      :: r2=40.0
end type init
type(init),save,protected :: guarded
contains
subroutine single_set(pin)
type(init),intent(in) :: pin
logical, save :: firsttime=.true. ! can only call once
! guarded=init(1,2,3.0,4.0) ! could read from command line or config file (NAMELIST?) ...
if(firsttime)then
   guarded=pin
firsttime=.false.
else
   write(*,*)'nevermore'
   stop
endif
end subroutine single_set
end module M_immutable
program testit
use M_immutable, only : guarded, init, single_set
write(*,*)guarded%i1, guarded%i2, guarded%r1,guarded%3 ! they have defaults
call single_set(init(1,2,3.0,4.0)) ! you can set them only once
!guarded%1=10  ! this would be caught at compile time
write(*,*)guarded
call single_set(init(11,22,33.0,44.0)) ! setting it twice stops program at runtime
end program testit

sans typos (no compiler handy) this catches changing them at compile time (better than asked for) lets them only be set once at run time, sets initial values but lets them be changed by setting them (which could be by command line parameters or config files or any other method).

In this version, just add a value to the type and init it to add a new guarded value, and it it a matter of taste, but the guarded%NAME syntax makes it clear it is one of your guarded values,and makes name collisions unlikely.

you can use parameter names on the init to make it clearer what is being set and depending on taste could make FIRSTIME global in the module and have different init methods like “read from NAMELIST file” or “read from command line” as well as the “pass in values method” and they could see if any of the others had been called and only allow one initialization; but I am not sure if that is what you meant, or make the input PIN be optional and use another method if not set, … and so on.

My problem with ASSOCIATE is having to potentially put it in a lot of routines, and that it also requires an associated (:>) END ASSOCIATE. If there are not a lot of routines using these values (that I assume are in some respect global, as you mention reading them from a config file or the command line)) that might not be an issue; ASSOCIATE could work nicely if all read in early in the main routine and then passed only as parameters to the rest of the procedures.

2 Likes

So the module can set defaults and does not need recompiled for the calling program(s) to change the values. The user can subsequently change the values
by name on the command line. Values can be changed only via a call to SINGLE_SET(), which can be called only once. Trying to set the values anywhere in the user program is caught as an error at compile time.

 ./a.out times=100 width=200
           1           2   3.00000000       4.00000000    
         100           2   200.000000       20.0000000  

perhaps not as nice as a new variable properly like “IMMUTABLE”, but not too bad in less than one page of code.

module m_immutable
implicit none
private
public guarded , init , single_set
type :: init
   integer  ::  times = 1, flag = 2
   real     ::  width = 3.0, height = 4.0
end type init
type (init) , save , protected :: guarded
contains

subroutine single_set(pin)
implicit none
type (init) , intent(in) :: pin
logical , save     :: firsttime = .true.  ! flag so can only call once
namelist /args/ guarded
character(len=256) :: input(3)=[character(len=256) :: '&args','','/']
character(len=256) :: message
integer            :: i, ios
   if ( firsttime ) then
      guarded=pin ! apply values from call
      ! read arguments from command line
      do i=1,command_argument_count()
         call get_command_argument(i,input(2))
         input(2)='guarded%'//adjustl(input(2))
         read(input,nml=args,iostat=ios,iomsg=message)
         if(ios.ne.0)then
            write(*,*)'ERROR:'//trim(message)
            stop 2
         endif
      enddo
      firsttime = .false.
   else
      write (*,*) 'nevermore'
      stop
   endif
end subroutine single_set
end module m_immutable

program testit
use m_immutable , only: g=>guarded , init , single_set
implicit none
   write (*,*) g%times , g%flag , g%width, g%height
   call single_set(init(width=10.0,height=20.0))
   write (*,*) g
end program testit

And for fun, add reading a config file called “.config” from the current directory in
NAMELIST format, like:

&args
guarded%width=1000.0
guarded%height=2000.0
/
add reading a config file
module m_immutable
implicit none
private
public guarded , init , single_set
type :: init
   integer  ::  times = 1, flag = 2
   real     ::  width = 3.0, height = 4.0
end type init
type (init) , save , protected :: guarded
contains

subroutine single_set(pin)
implicit none
type (init) , intent(in) :: pin
logical , save     :: firsttime = .true.  ! flag so can only call once
namelist /args/ guarded
character(len=256) :: input(3)=[character(len=256) :: '&args','','/']
character(len=256) :: message
integer            :: i, ios, lun
logical            :: exist
   if ( firsttime ) then
      guarded=pin ! apply values from call
      inquire(file='.config',exist=exist)
      if(exist)then
         open(file='.config',newunit=lun,iostat=ios,iomsg=message)
         call checkios()
         read(lun,nml=args,iostat=ios,iomsg=message)
         call checkios()
      endif
      ! read arguments from command line
      do i=1,command_argument_count()
         call get_command_argument(i,input(2))
         input(2)='guarded%'//adjustl(input(2))
         read(input,nml=args,iostat=ios,iomsg=message)
         call checkios()
      enddo
      firsttime = .false.
   else
      write (*,*) 'nevermore'
      stop
   endif
contains
subroutine checkios()
   if(ios.ne.0)then
      write(*,*)'ERROR:',trim(message)
      stop
   endif
end subroutine checkios
end subroutine single_set
end module m_immutable

program testit
use m_immutable , only: g=>guarded , init , single_set
implicit none
   write (*,*) g%times , g%flag , g%width, g%height
   call single_set(init(width=10.0,height=20.0))
   write (*,*) g
end program testit
3 Likes

As remarked by @Beliavsky, the code is invalid: it is trying to change the value of an expression. If you compile it (add declarations and such as necessary/desirable), the compiler will complain about that, showing that you cannot change the value of xx. This shows it is not necessary to come up with an arithmetic expression like x+0.0 to force this behaviour.

Then again, the protected attribute is well worth considering - see the rest of the thread.

2 Likes

@urbanjost I like the single_set solution. @Arjen thank you, useful comments.

1 Like

FWIW the Eiffel language has the ONCE attribute for variables like that.

2 Likes

Nice name for it, like that much better than IMMUTABLE. Anyone know of any other language with that attribute? I do not think I ever looked for it, but that would give more weight to adding a new attribute to types.

PS: It occurred to me later that the ASSOCIATED method would make it more likely the compiler would optimize the values more like a PARAMETER value than the PROTECTED method.

1 Like

What about const as in C and C++? In C++ you can write

#include <iostream>
#include <cstdlib>
#include <ctime>

int main()
{
	srand(time(0));
	int i = rand();
	const int j = i + 1;
	std::cout << i << " " << j;
	// invalid: j = j + 1;
}
1 Like

IMHO C/C++ const is pretty much the same as Fortran PARAMETER

1 Like

It is more flexible, as shown in my example. I believe a Fortran PARAMETER must be known at compile time.

1 Like

Yes, you’re right. So the PARAMETER is more like C’s #define-d macro.

Maybe the protected atribute could serve for that purpose inside a procedure?

1 Like

For the case where a program reads command line arguments that are never changed, the program could be a shell that just gets the command line arguments and passes them to a subroutine where they are intent(in) arguments. That subroutine acts like a main program, calling other procedures to do the work. Below, by declaring the main subroutine impure elemental, it is easy to have many runs of the programs with different inputs, a common scenario.

module main_mod
implicit none
contains
impure elemental subroutine main(c1,c2)
real, intent(in) :: c1,c2
print*,"in subroutine main, c1, c2 =",c1,c2
! rest of program will be here, including calls to other procedures
end subroutine main
end module main_mod

program get_arg
use main_mod
implicit none
integer, parameter  :: narg = 2
character (len=100) :: arg
real                :: c1,c2
call get_command_argument(1,arg)
read (arg,*) c1
call get_command_argument(2,arg)
read (arg,*) c2
call main(c1,c2)
end program get_arg
3 Likes

There are many versions of const in C & C++:

These are basically a PARAMETER in Fortran (each with its own nuances):

/* C and C++ */
#define SOME_CONSTANT 1
const int SOME_CONSTANT = 1; 
enum {
    SOME_CONSTANT = 1
};

// C++ only
constinit int SOME_CONSTANT = 1;
constexpr int SOME_CONSTANT = 1;
consteval int SOME_CONSTANT = 1;

In a function body, you can declare a variable as const by creating the variable when you need it. There are some schools of thought that espouse making this the default.

void some_function() {
    const int immutable_variable = function_returning_an_int();
    ...
}

In a struct/class, you can declare a member to be const and it has to be set when the struct is created. (Ignoring the ability to cast away ‘constness’, which is almost always a code smell and can cause bad things to happen when it hits the compiler).

typedef struct {
    const int immutable_variable;
} SomeStruct;

SomeStruct s = {1};

And I won’t go into const member functions of structs/classes in C++, const pointers/references, or const function arguments.

Rust makes all variables const by default, using the let keyword, and you have to ‘opt-in’ to mutability using the mut keyword:

let immutable_variable = function_returning_an_int();   
let mut mutable_variable = function_returning_an_int();

And it also has PARAMETER like constants:

const SOME_CONSTANT: int = 1;

One interesting feature of Rust allows you to separate the declaration from the definition of the variable, even though the variable is immutable:

let immutable_variable;
// Lots of other stuff happens
immutable_variable = function_returning_an_int();

The rust compiler does an analysis of the function to make sure that the immutable variable can only be set once, through all code paths, and will complain otherwise.

4 Likes