j3-fortran / fortran_proposals

Proposals for the Fortran Standard Committee
177 stars 15 forks source link

Extend formatted derived type input/output to default edit descriptors #288

Open perazz opened 1 year ago

perazz commented 1 year ago

A severe limitation of the current standard's derived-type I/O facility is the lack of backward compatibility with non-derived types; I propose all standard edit descriptors to also become valid edit descriptors for DTIO. This would allow the Fortran programmer to define derived types, possibly replacing former numeric/character variables, without breaking existing code.

The current standard already defines an iotype input variable to the interface for formatted io:

subroutine formatted_io(dtv,unit,iotype,v_list,iostat,iomsg)

Current options for iotype are:

However, none of the standard edit descriptors for numeric and character variables are possible (e, f, g, b, a, etc.).

Imagine a case where a string wrapper is being implemented to replace character arrays:

type :: string
   character(len=:), allocatable :: text
   contains
      procedure, private :: ws
      generic :: write(formatted) => ws
end type string

It would be natural to use the standard A edit descriptor whenever strings are being used, instead of the cumbersome DT one:

type(string) :: s
write(my_unit,'(A,1x,A)') 'my string is:',s

The same would apply to derived types representing quantities, which are common in scientific computing:

type :: my_quantity
   real :: r1
   real :: r2
end type my_quantity

One will most likely want to output them as either character variables, or numeric values:

type(my_quantity) :: q
write(my_unit,'e10.3') q
klausler commented 1 year ago

The non-DT editing descriptors already have a well-defined meaning for derived types. This works in 5 of 6 Fortran compilers that I tried:

type mytype
  real x, y
end type
print "(*(F8.3))", mytype(1.23,4.56)
end

How will you distinguish your new semantics to avoid changing current behaviors?

jacobwilliams commented 1 year ago

Oh wow. I didn't know you could do that! Is that actually in the Fortran standard?

klausler commented 1 year ago

Oh wow. I didn't know you could do that! Is that actually in the Fortran standard?

You bet; see 12.6.3 paragraph 7.

FortranFan commented 1 year ago

Oh wow. I didn't know you could do that! Is that actually in the Fortran standard?

Re: "Is that actually in the Fortran standard?" - yes, since the Fortran 90 revision, if I'm not mistaken i.e., except for the unlimited repeat editing with '(*(..)'.

Re: "Oh wow," oh, try the following now:

type mytype
  real, allocatable :: x, y
end type
print "(*(F8.3))", mytype(1.23,4.56)
end
perazz commented 1 year ago

The non-DT editing descriptors already have a well-defined meaning for derived types. This works in 5 of 6 Fortran compilers that I tried:

type mytype
  real x, y
end type
print "(*(F8.3))", mytype(1.23,4.56)
end

How will you distinguish your new semantics to avoid changing current behaviors?

Thank you, what you're saying is we can always use "standard", non-derived-type IO for all components of the derived type. Now though, if I define DTIO for the derived type that option becomes not available anymore with gfortran :

module myt
  type mytype
          real x, y
          contains
          procedure :: writeform
          generic :: write(formatted) => writeform
  end type
  contains
  subroutine writeform(dtv,unit,iotype,v_list,iostat,iomsg)
     class(mytype), intent(in) :: dtv
     integer, intent(in) :: unit
     character(*), intent(in) :: iotype
     integer, intent(in) :: v_list(:)
     integer, intent(out) :: iostat
     character(*), intent(inout) :: iomsg
     write(unit,'(A)',iostat=iostat,iomsg=iomsg) 'called from DTIO'
  end subroutine writeform
end module
program test_dt
      use myt
    implicit none
print "(*(F8.3))", mytype(1.23,4.56)
end program test_dt

This program stops with error

At line 24 of file test_dt.f90 (unit = 6, file = 'stdout')
Fortran runtime error: Expected REAL for item 1 in formatted transfer, got CLASS or DERIVED
(*(F8.3))
   ^

...and there is no way to get that back via DTIO. That's exactly the problem I'm trying to explain. In Metcalf/Reid/Cohen I can't find mention of it, so my guess is gfortran is doing it right, and this is a loophole in the standard?

everythingfunctional commented 1 year ago
  • DT//string, where "string" contains options for the DT edit descriptor.

Note, any "options" come from the v_list argument. They are not included in the iotype string. However, I don't see any reason "F", "A", or any other intrinsic edit descriptor couldn't be handled the same way. It would then be the responsibility of the DTIO implementer to catch invalid edit descriptors and set iostat and iomsg accordingly.

klausler commented 1 year ago

You're running into a gfortran bug with that program; the program is conforming. Try ifort, ifx, NAG, or XLF.

I repeat: Derived type I/O list items already have an interpretation for format-driven editing descriptors other than DT, whether the derived type has UDDTIO procedures or not. If you want UDDTIO procedures to be called for edit descriptors other than DT, you'll need some kind of new syntax to select the new behavior.

perazz commented 1 year ago

Could the extension be limited to the (a) edit descriptor?

everythingfunctional commented 1 year ago

I think you'll still have the same problem. I.e.

type mytype
  character(len=:), allocatable :: x, y
end type
print "(*(A))", mytype("Hello", "World")
end

already has a meaning.