The F Programming Language Tastes Like Java

Walt Brainerd
David Epstein
Richard Hendrickson

Imagine1, Inc.


This article appeared in the 1997 September issue of MacTech Magazine Vol. 13, No. 9, pages 30-39.

This article launches the startling comeback of Fortran by discussing the features found in the F subset of Fortran 95 along with the features found in Java. The strengths of Java and the F programming language make a wonderful combination for beginners to professional programmers working on large projects.

Both languages claim multi-platform portability. Java capitalizes on the look and feel of C++ while aiming at professional programmers; F capitalizes on the efficiency and pedagogical design of Fortran 95 while aiming at teenage beginners, experienced scientists, engineers and large teams of programmers. Java offers powerful object-oriented capabilities familiar to the C++ community; F offers organized module-oriented capabilities familiar to the Fortran 95 community.

Getting over the initial shock that Fortran is not all that different than Java can be a stretch for any programmer not in a coma since 1995. The amazing similarities surprised even the authors who had brought together over four decades of Fortran language design committee experience to create F while the Green team was creating Java.

The simple step taken by both language design teams is a move away from procedures as a first class citizen and the insistence that procedures be encapsulated inside a more powerful mechanism-- a class in Java and a module in F.

After comparing the features of F and Java, we suggest some benefits of combining F and Java for introductory programming instruction as well as for large professional projects and efficiency driven applications.

A Binary Tree

An example may help to show the similarities between F and Java. Diagram #1 shows a binary tree written in both F and Java. Notice that the mythical phrase, "Java does not have pointers", is better said as, "Java does not support the dangerous pointer arithmetic found in C and C++". In F, the word "pointer" is actually used in the declaration of the "nested defined type" (for lack of a better term) found in Java.

Diagram #1 A binary tree written in both F and Java

! A Binary Tree in F                           // A Binary Tree in Java
module m_binary_tree                           class Tree {
 public :: main, Insert, PrintTree         
 private :: NewTree                            
  type, public :: node                         
   integer :: value                              int value;
   type (node), pointer :: left, right           Tree left, right;
  endtype node
   function NewTree(num) result(tree)            Tree (
     integer, intent(in) :: num                   int num) {
     type (node), pointer :: tree
      allocate (tree)                          
      tree%value = num                             value = num;
      nullify(tree%left)                           left = null;
      nullify(tree%right)                          right = null;
   endfunction NewTree                           }//Constructor

   recursive subroutine Insert(tree, num)        static Tree Insert(
     type (node), pointer :: tree                 Tree tree,
     integer, intent(in) :: num                   int num) {
      if (.not. associated (tree)) then            if (tree == null) {
         tree = NewTree(num)                         return new Tree(num);
      elseif (num < tree%value) then               } elseif (num < tree.value) {
         call Insert(tree%left)                      tree.left = Insert(tree.left, num);
                                                     return tree;
      else                                         } else {
         call Insert(tree%right)                     tree.right = Insert(tree.right, num);
                                                     return tree;
      endif                                        }//if
   endsubroutine Insert                          }//Insert

   recursive subroutine PrintTree(tree)          static void PrintTree(
     type (node), pointer :: tree                 Tree tree) {
      if (associated (tree)) then                  if (tree != null) {
         call PrintTree (tree%left)                   PrintTree(tree.left);
         print *, tree%value                          System.out.println(tree.value);
         call PrintTree (t%right)                     PrintTree(tree.right);
      endif                                        }//if
   endsubroutine PrintTree                       }//PrintTree

   subroutine main                                public static void main (String argv[]) {
     type (node), pointer :: tree                   Tree tree;
     integer :: i                                   int i;
      nullify (tree)                                 tree = null;
      print *, "Unsorted list"                       System.out.println ("Unsorted list");
      do i = 1, 30, 3                                for (i=1; i<3*10; i=i+3) {
         print *, modulo(i,10)                          System.out.println (i%10);
         call insert(tree, modulo(i,10))                tree = Insert(tree, i%10);
      enddo                                          }//for
      print *, "Sorted list"                         System.out.println ("Sorted list");
      call print_tree (tree)                         PrintTree(tree);
   endsubroutine main                             }//main
endmodule m_tree_sort                          }//Tree

program tree_sort
   use m_tree_sort
    call main()
endprogram tree_sort

Notice the statements that declare the recursive type:

In F

type (node), pointer :: left, right  !  found inside "node"

In Java

   Tree left, right;                 // found inside "Tree"

The strategy for pointers in F and Java is actually the same--no physical address is made available and what is being pointed at must be specified in the declaration (that is, no void pointers). In F, pointers can point only to objects that are targets; in Java, everything is being pointed at, even procedures. A non-mainstream view of Java is not that there are no pointers, but that everything in Java is being pointed at.

The F Programming Language

We expect the average reader to be more familiar with Java than with the F programming language. Thus, an introduction to some of the F programming language is called for.

One major difference between F and Java is that Java technology often refers to much more than a programming language--the class hierarchy from which all objects are derived, the Java Virtual Machine(JVM), bytecode and Just-In-Time compilers to name just a few. F, on the other hand, does not have a standard module hierarchy or set of software tools considered part of the F technology other than the F compilers. This overview of F is thus a description of the F programming language. For those seeking more specifics, the grammar for F (in BNF) can be found on the Fortran web page.

F Statements

Diagram #2 categorizes all the F statements. Instead of calling a procedure "main" as in Java, there is a single main "program" required in F and it can be given any name. An F program can use modules and reference public procedures and other public entities from the used modules. Modules and their contained procedures can also use other modules. This creates module hierarchies whose roots are the modules that were used in the program. Using a module is similar to importing a package in Java. Both languages encourage a hierarchical organization.

Diagram #2 Categorizing all the F statements

Organizational Constructs

  use ... module
 endprogram        public :: proc-list
                   private :: proc-list
                  endmodule        use module        use module
                                  endsubroutine     endfunction

Action Constructs

 if / elseif / else / endif
 select case / case / case default / endselect
 do / cycle / exit / enddo
 where / elsewhere / endwhere


 type       integer   character   intrinsic    interface
 endtype    real      logical                   module procedure
            complex                            endinterface


 = (assignment)            allocate          call      stop
 => (pointer assignment)   deallocate        return


 print    open    write    inquire     backspace
 read     close                        rewind

One design goal in F is to require explicit declarations for entities and not rely on any defaults. This is the opposite extreme from the "old Fortran" with its implicit declaration of an integer by starting its name with the letter "i" as in

itotal = 0 ! Declared an integer named itotal in older Fortran

One result of requiring everything to be explicitly stated is that the student's first module that contains a procedure will require a public statement. This leads into an early discussion of public and private if the instructor so chooses.

Those challenged to make a thorough comparison of F and Java will notice that Java does allow defaults and that the default for a function is protected. If Java was to employ the strategy used in F, the word "protected" would have been required to be explicitly specified.

The F Attributes

Diagram #3 lists the attributes for F entities. A little description of public and private is given here as these attributes differ slightly from the others.

Diagram #3 Attributes for F entities

 Attribute         Description

 pointer / target  Pointers can only point at objects that are targets.
 public / private  All module entities are either public or private.
 intent            All subroutine arguments must be declared as
                   intent in, out, or inout.
                   All function arguments must be intent in.
 dimension         Arrays can be from one to seven dimensions.
 allocatable       For dynamic allocation.
 parameter         A constant.
 save              A static local variable.

Procedures are listed in public or private statements in the module that defines them. All other module entities require a public or private attribute in their declaration statement. One other use of the word "private" is to declare the components of a public defined type as private to the module. Such a module would supply its users with access to the defined type and the procedures that operate on entities declared to be of this type. The insides of the type are hidden from the users of this module. An example of this can be seen in Diagram #4 which hides the internal parts of a car from its users.

Diagram #4 A public type with private components

module m_car
 use m_engine
 private ! makes sure not to export entities from module m_engine
 public :: GetCarInfo  !, ... other public procedures would be listed here
 private :: IncreaseCost  !, ... private procedures would be listed here
 type, public :: t_car
  private ! this public type has components private to this module
  type (t_engine) :: engine  ! t_engine imported from module m_engine
  integer :: cost_of_engine
  integer :: year
 endtype t_car
 subroutine GetCarInfo(engine, year)
 ! ...
 subroutine IncreaseCost()
 ! ...
endmodule m_car


Fundamental Difference between F and Java

Although there are many similarities between F and Java, there is one fundamental design difference and a few minor differences that deserve attention.

The fundamental difference between F and Java is the way in which modules and classes are accessed. In F, a module can be used, which makes its public entities available. There are two sorts of modules worth mentioning for this discussion:

  1. declare actual data entities to be shared amongst its users
  2. provide the definition of types and procedures that operate on those types, requiring the users to declare the actual data entities and call procedures accordingly.

In Java, a class can be imported which makes its definition available for instantiations. This is similar to the second sort of F module listed above. Namely, the Java class definition provides a type and procedures that operate on that type but it is left to the importer of this class to create instantiations and call procedures accordingly.

Although modules are similar, the fact that they are used and classes are instantiated leads to a slight shift in organizational thinking. For example, if SetSalary is a procedure that sets the salary of an employee and employee is part of a person, a Java reference may look like


and an F reference may look like

call SetSalary(anEmployee, 100)

In Java, anEmployee would automatically inherit the features of a person; in F, it is up to the designer of the employee module to use the person module and make appropriate features of a person available to employees. For example, if Gender is a feature of a person, a Java reference may look like

and an F reference may look like

Name Space

While F references do not have dots, F presents a possible name space problem since the same name cannot be public in more than one used module within the scope of the use statements. The solution for this in F is the feature of renaming used entities. For example,

use employee_module, s1=>salary

makes the salary entity of the employee module available only as the name s1 in the scope of this use statement.

Cats, Dogs, and Mammals

A more philosophical view of comparing module-oriented programming and object-oriented programming can be shown using the example of cats and dogs. One argument for object-oriented programming is that humans see the world in terms of objects instead of in terms of actions. We see cats and dogs instead of GiveBath, HuntBird, WagTail and SniffHand. This argument also holds for module-oriented programming with the word "module" replacing the word "object"--humans see the world in terms of modules instead of in term of actions. Seeing cats and dogs as modules means viewing them as concepts which have definitions; it is possible to have an actual cat or dog object, but it is also possible to talk about them without having to create actual cat or dog .

The implementation difference can be found by taking a closer look at the writing and accessing of cats and dogs. In F, the implementor of cats and dogs can decide to use mammals to gain access to concepts common to all mammals. An F cat module cannot, however, export the mammal public entities. If the cat module wants to export a trait common to mammals such as SuckMilk, the implementor of the cat module must do some work to make this happen. In Java, when the cat class inherits from the mammal class, the cat class automatically exports all the public concepts of the mammal class. The access to cats in F is done by using the cat module and possibly calling KittenSucksMilk(aCat). The access to cats in Java is done by instantiating a cat and possibly calling aCat.SuckMilk.

Both methods have their advantages and disadvantages. Do you see cats and dogs or do you see mammals that are cats and dogs? In F, the designer of cats and dogs decides if it is important that they are mammals; in Java, the user of cats and dogs needs to understand that they are mammals.

Other Differences between F and Java

Having hurdled the fundamental difference of using modules versus instantiating classes, other differences are minor jumps in comparison.

Intrinsic Procedures Versus Standard Libraries

F provides more than one hundred intrinsic procedures such as cos, tan, len and date_and_time. These intrinsic procedures are part of the F programming language and not part of a standard module that needs to be included or is implicitly included. Java provides much more than intrinsic procedures with its whole class hierarchy providing "standard" libraries for GUI building, exception handling, graphics, networking, multi-threading and coercion such as integers to characters. This Java library will surely expand for whatever else becomes important (such as multimedia). To reference these goodies, the appropriate classes or packages must be imported. A wildcard of "*" can be used to simply import everything at the expense of both size and speed of the application.

Overloaded Operators and Overloading Procedures

Both F and Java allow using the same generic name to reference one of a group of specific procedures. Diagram #5 shows overloading the generic name "swap" for the F intrinsic types. Overloading operators is a different story.

Diagram #5 Overloading Swap

!--------------------------generic swap routines
module  swap_routines
 public  ::  swap

  interface swap
    module procedure  int_swap, real_swap, complex_swap,   &
                      logical_swap, character_swap
  end interface !swap


subroutine int_swap (a,b)
  integer,  intent(in out)  ::  a,b
  integer                   ::  t
   t = a
   a = b
   b = t
endsubroutine int_swap

subroutine real_swap (a,b)
  real,  intent(in out)  ::  a,b
  real                   ::  t
   t = a
   a = b
   b = t
endsubroutine real_swap

subroutine complex_swap (a,b)
  complex,  intent(in out)  ::  a,b
  complex                   ::  t
   t = a
   a = b
   b = t
endsubroutine complex_swap

subroutine logical_swap (a,b)
  logical,  intent(in out)  ::  a,b
  logical                   ::  t
   t = a
   a = b
   b = t
endsubroutine logical_swap

subroutine character_swap (a,b)
  character(len=*),  intent(in out)  ::  a,b
  character(len=1)                   ::  t
   if (len(a) == len(b) ) then
     do i = 1, len(a)
       t = a(i:i)
       a(i:i) = b(i:i)
       b(i:i) = t
     end do
     print *, "length of character strings differ, can't swap"
   end if
endsubroutine character_swap

end module swap_routines

program try_swap
 use swap_routines
  integer  :: i1, i2
  character (len=16)  :: c1, c2

   i1 = 10
   i2 = 20
   c1 = "string1"
   c2 = "the other string"

   print *, "i1 and i2 before swap", i1, i2
   call swap (i1, i2)
   print *, "i1 and i2 after  swap", i1, i2

   print *, "c1 and c2 before swap>", c1,"< >", c2,"<"
   call swap (c1, c2)
   print *, "c1 and c2 after  swap>", c1,"< >", c2,"<"

end program try_swap

F provides the ability to overload the intrinsic operators as long as the operators are not already part of F. For example, "+" can be overloaded to operate on two characters but it cannot be overloaded to operate on two integers as this would replace the intrinsic addition operation already found in F. F also provides the ability to create your own unary and binary operators as long as they are given names surrounded by dots as in .inverse..

When overloading a name or an operator, all desired permutations of intrinsic and user defined types must be listed so that resolution to a specific function is always known at compile time; there are no run time resolutions in F. We can only guess that the Green team decided to do away with overloading operators out of the frustration created by C++ when a programmer attempts to figure out what A()+B() actually means. With its virtual functions, the expression A()+B() in C++ could be anything from a simple addition to a call to practically any function. It may require a thorough understanding of the design and most of the code to be sure exactly what A()+B() means. In F, one can conceivably click on the "+" and be directed to the specific function that is being referenced.

Kinds Versus Specific Arithmetic

Java solves the unportability created by different machine's mathematical models by requiring a specified range and precision for each type; if machines do not match this mathematical model, they must emulate it. F solves this same problem by allowing specifications of different kinds for types and literals. A kind number can be determined using the intrinsic procedures. For example, the expression


returns a kind number to specify variables to have at least 10 digits of precision and an exponent range of at least 50. The kind number is then used in the declaration. For example:

module kind_module
 integer, public, parameter :: k10_50 = selected_real_kind(10,50)
endmodule kind_module

module m_do_something_with_my_float
 use kind_module
 private ! makes sure not to export entities from kind_module
 real (kind = k10_50), public :: my_float
 ! ...
endmodule m_do_something_with_my_float

End-Of-Line Versus Semicolons

Unlike C, C++, and Java, statements in F conclude at the end-of-line unless an ampersand (&) is placed at the end-of-line to signal that this statement continues on the next line. Considering that most statements fit on one line, we feel that this design is easier for students to learn and less error-prone for both beginners and professionals. This may be a small point as today's C, C++ and Java programmers have obviously learned to cope with semicolons and since they are required inside certain statements (like the for statement) it appears that the Java designers had no choice.

Emphasis On Safety and Readability

The final difference between F and Java that we would like to mention is the overall strategy of safety and readability that was carefully designed into F. One way to compare this with the overall strategy of the "look-and-feel like C++" carefully designed into Java is that the F design is less marketable to today's programmers. Unfortunately for the software crisis, there are plenty more C++ programmers in the world than there are programmers that want to follow particular style guidelines.

For example, programmers with a preference for a one-line if statement do not care for the idea in F of requiring an endif. The required if-endif pair may lead to clearer blocks of code and unambiguous else statements, but more importantly it exemplifies the design strategy in F that abbrev.R!++ (abbreviations are not good). Requiring an endif statement in F sometimes means splitting one longer statement into three shorter statements by adding two words "then" and "endif". These words are important for error detection as well as tool writing. A missing parenthesis is easier to report if the reserved word "then" is required and a path coverage tool is easier to write if the word "endif" is required.

Our claim is that the safety and readability required of F code aid both students and professionals. Possibly the best example of the safety designed into F is the intrinsic statement. Intrinsic procedures in F are allowed to be overloaded as long as the argument types differ from those found as part of the F language definition. For example, cos can be overloaded to accept a character argument but it cannot be overloaded to accept a real argument. Since there are so many intrinsic procedures, we decided that overloading one of them requires listing the name of the intrinsic on an intrinsic statement. This prevents the unknowing programmer from creating an involved module using a name that is already part of the language. Without this requirement, it would have been possible to overload cos, presume a reference to it, accidentally use the wrong argument and wind up referencing the intrinsic without ever realizing it.

Attention Teachers and Beginners

Data Encapsulation

It seems that right now everybody on the planet is interested in learning Java. Java has become so popular that many colleges and high schools are dropping Pascal and throwing beginners directly into Java. Though we believe Java is much more structured and friendly than C++, the fact remains that beginners need nurturing error messages more than the semester's survivors need the promise of a potential summer job. Indeed, good teachers can teach anything and good students can learn anything, but F offers a chance for those in the middle of the bell curve to learn how to program.

F jumps in between the academic emphasis of Pascal and the professional look and feel of Java to offer a compromise designed for both camps and ideally suited for potential engineers and scientists. The most attractive feature of object-oriented programming for beginning programming courses is data encapsulation. With its module-oriented programming, F is ideally suited for teaching and learning data encapsulation without getting lost in complicated inheritance chains.

Nurturing Versus Sink-or-Swim

Keep in mind that pure beginners have dozens of skills to learn, including