The More Things Change: Fortran Edition
Technology improves over time. Storage capacity increases. Spinning platters are replaced with memory chips. CPUs and memory get faster. Moore’s Law. Compilers and languages get better. More language features become available. But do these changes actually improve things? Fifty years ago, meteorologists used the best mainframes of the time, and got the weather wrong more than they got it right. Today, they have a global network of satellites and supercomputers, yet they’re wrong more than they’re right (we just had a snowstorm in NJ that was forecast as 2-4", but got 16" before drifting).
As with most other languages, FORTRAN also added structure, better flow control and so forth. The problem with languages undergoing such a massive improvement is that occasionally, coding styles live for a very long time.
Imagine a programmer who learned to code using FORTRAN IV (variable names up to 6 characters, integers implicitly start with "I" through "N" and reals start with any other letter – unless explicitly declared, flow control via GOTO, etc) writing a program in 2000 (using a then-current compiler but with FORTRAN IV style). Now imagine some PhD candidate coming along in 2017 to maintain and enhance this FORTRAN IV-style code with the latest FORTRAN compiler.
A.B.was working at a university with just such a scientific software project as part of earning a PhD. These are just a couple of the things that caused a few head-desk moments.
Include statements. The first variant only allows code to be included. The second allows preprocessor directives (like
INCLUDE 'path/file' #include 'path/file'
Variables. Since the only data types/structures originally available were character, logical, integer, real*4, real*8 and arrays, you had to shoehorn your data into the closest fit. This led to declarations sections that included hundreds of basic declarations. This hasn’t improved today as people still use one data type to hold something that really should be implemented as something else. Also, while the compilers of today support encapsulation/modules, back then, everything was pretty much global.
Data structures. The only thing supported back then was multidimensional arrays. If you needed something like a map, you needed to roll your own. This looks odd to someone who cut their teeth on a version of the language where these features are built-in.
Inlining. FORTRAN subroutines support local subroutines and functions which are inlined, which is useful to provide implied visibility scoping. Prudent use allows you to DRY your code. This feature isn’t even used, so the same code is duplicated over and over again inline. Any of you folks see that pattern in your new-fangled modern systems?
Joel Spolsky commented about the value of keeping old code around. While there is much truth in his words, the main problem is that the original programmers invariably move on, and as he points out, it is much harder to read (someone else’s) code than to write your own; maintenance of ancient code is a real world issue. When code lives across too many language version improvements, it becomes inherently more difficult to maintain as its original form becomes more obsolete.
To give you an idea, take a look at the just the declaration section of one module that A.B. inherited (except for a 2 line comment at the top of the file, there were no comments). FWIW, when I did FORTRAN at the start of my career, I used to document the meaning of every. single. abbreviated. variable. name.
subroutine thesub(xfac,casign, & update,xret,typret, & meop1,meop2,meop12,meoptp, & traop1, traop2, tra12, & iblk1,iblk2,iblk12,iblktp, & idoff1,idoff2,idof12, & cntinf,reoinf, & strinf,mapinf,orbinf) implicit none include 'routes.h' include 'contr_times.h' include 'opdim.h' include 'stdunit.h' include 'ioparam.h' include 'multd2h.h' include 'def_operator.h' include 'def_me_list.h' include 'def_orbinf.h' include 'def_graph.h' include 'def_strinf.h' include 'def_filinf.h' include 'def_strmapinf.h' include 'def_reorder_info.h' include 'def_contraction_info.h' include 'ifc_memman.h' include 'ifc_operators.h' include 'hpvxseq.h' integer, parameter :: & ntest = 000 logical, intent(in) :: & update real(8), intent(in) :: & xfac, casign real(8), intent(inout), target :: & xret(1) type(coninf), target :: & cntinf integer, intent(in) :: & typret, & iblk1, iblk2, iblk12, iblktp, & idoff1,idoff2,idof12 logical, intent(in) :: & traop1, traop2, tra12 type(me_list), intent(in) :: & meop1, meop2, meop12, meoptp type(strinf), intent(in) :: & strinf type(mapinf), intent(inout) :: & mapinf type(orbinf), intent(in) :: & orbinf type(reoinf), intent(in), target :: & reoinf logical :: & bufop1, bufop2, buf12, & first1, first2, first3, first4, first5, & msfix1, msfix2, msfx12, msfxtp, & reject, fixsc1, fixsc2, fxsc12, & reo12, non1, useher, & traop1, traop2 integer :: & mstop1,mstop2,mst12, & igmtp1,igmtp2,igmt12, & nc_op1, na_op1, nc_op2, na_op2, & nc_ex1, na_ex1, nc_ex2, na_ex2, & ncop12, naop12, & nc12tp, na12tp, & nc_cnt, na_cnt, idxres, & nsym, isym, ifree, lenscr, lenblk, lenbuf, & buftp1, buftp1, bftp12, & idxst1, idxst2, idxt12, & ioff1, ioff2, ioff12, & idxop1, idxop2, idop12, & lenop1, lenop2, len12, & idxm12, ig12ls, & mscmxa, mscmxc, msc_ac, msc_a, msc_c, & msex1a, msex1c, msex2a, msex2c, & igmcac, igamca, igamcc, & igx1a, igx1c, igx2a, igx2c, & idxms, idxdis, lenmap, lbuf12, lb12tp, & idxd12, idx, ii, maxidx integer :: & ncblk1, nablk1, ncbka1, ncbkx1, & ncblk2, nablk2, ncbka2, ncbkx2, & ncbk12, nabk12, ncb12t, nab12t, & ncblkc, nablkc, & ncbk12, nabk12, & ncro12, naro12, & iblkof type(filinf), pointer :: & ffop1,ffop2,ff12 type(operator), pointer :: & op1, op2, op1op2, op12tp integer, pointer :: & cinf1c(:,:),cinf1a(:,:), & cinf2c(:,:),cinf2a(:,:), & cif12c(:,:), & cif12a(:,:), & cf12tc(:,:), & cf12ta(:,:), & cfx1c(:,:),cfx1a(:,:), & cfx2c(:,:),cfx2a(:,:), & cfcntc(:,:),cfcnta(:,:), & inf1c(:), & inf1a(:), & inf2c(:), & inf2a(:), & inf12c(:), & inf12a(:), & dmap1c(:),dmap1a(:), & dmap2c(:),dmap2a(:), & dm12tc(:),dm12ta(:) real(8) :: & xnrm, facscl, fcscl0, facab, xretls real(8) :: & cpu, sys, cpu0, sys0, cpu00, sys00 real(8), pointer :: & xop1(:), xop2(:), xop12(:), xscr(:) real(8), pointer :: & xbf1(:), xbf2(:), xbf12(:), xbf12(:), x12blk(:) integer :: & msbnd(2,3), igabnd(2,3), & ms12ia(3), ms12ic(3), ig12ia(3), ig12ic(3), & ig12rw(3) integer, pointer :: & gm1dc(:), gm1da(:), & gm2dc(:), gm2da(:), & gmx1dc(:), gmx1da(:), & gmx2dc(:), gmx2da(:), & gmcdsc (:), gmcdsa (:), & gmidsc (:), gmidsa (:), & ms1dsc(:), ms1dsa(:), & ms2dsc(:), ms2dsa(:), & msx1dc(:), msx1da(:), & msx2dc(:), msx2da(:), & mscdsc (:), mscdsa (:), & msidsc (:), msidsa (:), & idm1ds(:), idxm1a(:), & idm2ds(:), idxm2a(:), & idx1ds(:), ixms1a(:), & idx2ds(:), ixms2d(:), & idxdc (:), idxdsa (:), & idxmdc (:),idxmda (:), & lstrx1(:),lstrx2(:),lstcnt(:), & lstr1(:), lstr2(:), lst12t(:) integer, pointer :: & mex12a(:), m12c(:), & mex1ca(:), mx1cc(:), & mex2ca(:), mx2cc(:) integer, pointer :: & ndis1(:,:), dgms1(:,:,:), gms1(:,:), & lngms1(:,:), & ndis2(:,:), dgms2(:,:,:), gms2(:,:), & lngms2(:,:), & nds12t(:,:), dgms12(:,:,:), & gms12(:,:), & lgms12(:,:), & lg12tp(:,:), lm12tp(:,:,:) integer, pointer :: & cir12c(:,:), cir12a(:,:), & ci12c(:,:), ci12a(:,:), & mire1c(:), mire1a(:), & mire2c(:), mire2a(:), & mca12(:), didx12(:), dca12(:), & mca1(:), didx1(:), dca1(:), & mca2(:), didx2(:), dca2(:), & lenstr_array(:,:,:) c dbg integer, pointer :: & dum1c(:), dum1a(:), hpvx1c(:), hpvx1a(:), & dum2c(:), dum2a(:), hpvx2c(:), hpvx2a(:) integer :: & msd1(ngastp,2,meop1%op%njoined), & msd2(ngastp,2,meop2%op%njoined), & jdx, totc, tota c dbg type(graph), pointer :: & graphs(:) integer, external :: & ielsum, ielprd, imdst2, glnmp, idxlst, & mxdsbk, m2ims4 logical, external :: & nxtdis, nxtds2, lstcmp, & nndibk, nndids real(8), external :: & ddot