Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Derived type IO #312

Open
awvwgk opened this issue Feb 6, 2021 · 9 comments
Open

Derived type IO #312

awvwgk opened this issue Feb 6, 2021 · 9 comments
Labels
API Discussion on a specific API for a proposal (exploratory) implementation Implementation in experimental and submission of a PR

Comments

@awvwgk
Copy link
Member

awvwgk commented Feb 6, 2021

With types like the bitset_type it might be a really useful feature to use them in normal IO (read and write data transfer). The bitset_type for example implements a write_bitset interface, but could as well define the very same function as derived type IO:

An interface to a formatted write usually looks like

subroutine write_formatted(self, unit, iotype, v_list, iostat, iomsg)
  !> Instance of the object to write.
  class(*), intent(inout) :: self
  !> Formatted unit for output.
  integer, intent(in) :: unit
  !> Type of formatted data transfer, has the value "LISTDIRECTED" for fmt=*, "NAMELIST" for namelist output or starts with "DT" for derived type output.
  character(len=*), intent(in) :: iotype
  !> Rank one array of default integer type containing the edit descriptors for derived type output.
  integer, intent(in) :: v_list(:)
  !> Status identifier to indicate success of output operation.
  integer, intent(out) :: iostat
  !> Buffer to return error message in case of failing output operation.
  character(len=*), intent(inout) :: iomsg
end subroutine write_formatted

While an interface to an unformatted write is given here for short reference

subroutine write_unformatted(self, unit, iostat, iomsg)
  !> Instance of the object to write.
  class(*), intent(inout) :: self
  !> Formatted unit for output.
  integer, intent(in) :: unit
  !> Status identifier to indicate success of output operation.
  integer, intent(out) :: iostat
  !> Buffer to return error message in case of failing output operation.
  character(len=*), intent(inout) :: iomsg
end subroutine write_unformatted

This would allow to easily write a bitset with

use stdlib_bitset, only : bitset_type
type(bitset_type) :: bitset
! ...
print*, bitset
end
@milancurcic
Copy link
Member

Yes, this looks like it would be a useful addition.

@jvdp1
Copy link
Member

jvdp1 commented Feb 8, 2021

I agree with @milancurcic: it would be a nice addition.

@awvwgk
Copy link
Member Author

awvwgk commented Feb 8, 2021

Just a practical question, how would one document the API of such procedure?

The official name is user defined derived type input output, which shortens to UDDTIO, which is a completely unwieldable abbreviation in my opinion and I'm saying this coming quantum chemistry background ;).

My suggestion would be something along the lines of this:

### Formatted read

#### Description

Read a character sequence from a connected formatted unit into the string.

#### Syntax

`read(unit, fmt, iostat=iostat, iomsg=iomsg) string`

#### Status

Experimental

#### Class

Formatted derived type IO.

#### Argument

- `string`: Instance of the string type to read. This argument is `intent(inout)`.
- `unit`: Formatted unit for input. This argument is `intent(in)`.
- `iotype`: Type of formatted data transfer, has the value `"LISTDIRECTED"` for `fmt=*`,
  `"NAMELIST"` for namelist input or starts with `"DT"` for derived type input.
  This argument is `intent(in)`.
- `v_list`: Rank one array of default integer type containing the edit descriptors for
  derived type input.
  This argument is `intent(in)`.
- `iostat`: Status identifier to indicate success of input operation.
  This argument is `intent(out)`.
- `iomsg`: Buffer to return error message in case of failing input operation.
  This argument is `intent(inout)`.

#### Example

```fortran
use stdlib_string_type
implicit none
type(string_type) :: string
integer :: io
string = "Important saved value"

open(newunit=io, form="formatted", status="scratch")
write(io, *) string
write(io, *)

rewind(io)

read(io, *) string
close(io)
end
```

@milancurcic
Copy link
Member

Well, these would be internal so they wouldn't have a user facing API, right? For example, everything in the Arguments section is irrelevant for the user, unless I'm unaware of some feature of UDDTIO. I think the following sentence or similar, in the derived type docstring would suffice:

This derived type also implements I/O procedures, so its instance can be used in read and write statements.

@awvwgk
Copy link
Member Author

awvwgk commented Feb 8, 2021

At least v_list and iotype can be used to influence the format when using the dt format specifier, they might be ignored, used to change the width of the output or cause a runtime error (= custom iostat + iomsg) if invalid input is provided. Also in case of unformatted read and write the layout could be described or left implementation defined.

We should of course start simple first, just putting in a note for formatted/unformatted read/write is sufficient for now, but there are details which could become important / useful later.

@wclodius2
Copy link
Contributor

As long as I am going to address issue #383 for bitsets, I can try to add the appropriate UDDTIO for them. However I have never used or written UDDTIO routines. Does someone have a good reference on how to write and test them?

@ivan-pi
Copy link
Member

ivan-pi commented Apr 30, 2021

@wclodius2 , I am only aware of the Section 11.6 "Defined derived-type input/output" in MRC (the red book). You can also follow the string_type module:

interface write(formatted)

The formatted interfaces will look like:

    interface write(formatted)
        module subroutine write_form_bitset_64(bitset, unit, iotype, &
            v_list, iostat, iomsg)
            class(bitset_64), intent(in) :: bitset
            integer, intent(in) :: unit
            character(len=*), intent(in) :: iotype
            integer, intent(in) :: v_list(:)
            integer, intent(out) :: iostat
            character(len=*), intent(inout) :: iomsg
        end subroutine
        module subroutine write_form_bitset_large(bitset, unit, iotype, &
            v_list, iostat, iomsg)
            class(bitset_large), intent(in) :: bitset
            integer, intent(in) :: unit
            character(len=*), intent(in) :: iotype
            integer, intent(in) :: v_list(:)
            integer, intent(out) :: iostat
            character(len=*), intent(inout) :: iomsg
        end subroutine
    end interface

The actual implementation can go in the submodule (only shown here for bitset_64)

    module subroutine write_form_bitset_64(bitset, unit, iotype, v_list, &
        iostat, iomsg)
        class(bitset_64), intent(in) :: bitset
        integer, intent(in) :: unit
        character(len=*), intent(in) :: iotype
        integer, intent(in) :: v_list(:)
        integer, intent(out) :: iostat
        character(len=*), intent(inout) :: iomsg

        character(len=8) :: fmt

        write(fmt,'(A2,I0,A1)') '(B', bitset%bits(),')'
        
        select case(iotype)
        case("LISTDIRECTED")
            write(unit, fmt, iostat=iostat, iomsg=iomsg) bitset%block
        case("NAMELIST")
            error stop "[Fatal] This implementation does not support namelist output"
        case default ! DT*
            select case(size(v_list))
            case(0) ! DT
                write(unit, fmt, iostat=iostat, iomsg=iomsg) bitset%block
            case(1)
                if (v_list(1) > bitset%bits()) then
                    write(fmt,'(A4,I0,A1)') '(B0.',v_list(1),')'
                end if
                write(unit, fmt, iostat=iostat, iomsg=iomsg) bitset%block
            case default
                error stop "[Fatal] This implementation does not support v_list formatters"
            end select
        end select
    end subroutine

The error checking and messages could use further improving.

Press here for an example
program bitset_example1

  use, intrinsic :: iso_fortran_env, only: output_unit
  use stdlib_bitsets
  implicit none

  integer, parameter :: BIT_SZ = 5
  type(bitset_64) :: bitvec
  character(len=:), allocatable :: bitstr
  integer :: i

  call bitvec%init(BIT_SZ) ! all zeros by default

  call bitvec%set(0)
  call bitvec%set(1)
  call bitvec%set(4)

  write(*,'(*(I1))') (bitvec%value(i), i = 0, BIT_SZ-1)

  call bitvec%to_string(bitstr)
  write(*,'(A)') bitstr

  print *, bitvec
  write(*,'(dt(8))') bitvec ! field of width is 8

end program

Output:

$ gfortran -Wall bitset_example1.f90 -o example1 -I "/home/ipribec/.local/include" -L"/home/ipribec/.local/lib" -lfortran_stdlib
$ ./example1 
11001
10011
 10011
00010011

@wclodius2
Copy link
Contributor

@ivan-pi thanks! I am a bit busy to have more than one git iron in the fire at a time, but once the sorting PR is approved this will be my next project.

@ivan-pi
Copy link
Member

ivan-pi commented May 1, 2021

There is no rush, I've been working on some small bitset example programs and I will probably open an issue with other suggestions soon.

With the UDDTIO usage is really simplified and it makes it feel much more natural! No need to use the string conversion routines and look-up the details of which procedure to call. A simple print or write will suffice.

@awvwgk awvwgk added meta Related to this repository API Discussion on a specific API for a proposal (exploratory) implementation Implementation in experimental and submission of a PR and removed meta Related to this repository labels Sep 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API Discussion on a specific API for a proposal (exploratory) implementation Implementation in experimental and submission of a PR
Projects
None yet
Development

No branches or pull requests

5 participants