Search code examples
fortrantostring

toString in Fortran


In Java there is a nifty function that any class descending from Object has:

public String toString()

the function in it self is not very magical, but the way that the Java interpreter uses it makes it very, well, useful. And, of course, this in not something that is of live-or-die importance, but a very nice-to-have, making life easier for somewhat lazy coders. As far as I can tell anything similar doesn't exist in Fortran (or in many other languages).

Am I wrong? Does something like this exist? Or, is there something similar hidden in Fortran lore?

For those who are not familiar with Java: Simply (but slightly inaccurate) said, if an object variable is used (without referring to any of its members) in any place that a String is expected, the Java interpreter looks up the toString() method for the variable's class, defaulting up through the hierarchy (all the way to Object). Thus, every object has some toString() available.

Implementing a toString() function into a class (or type) in Fortran is not much hazzle. Well, that kinda depends on the complexity of the type, and on what the user wants the function to return.

It would be far more convenient to write:

    write(*,*) somevariable

instead of

    write(*,*) somevariable%toString()

similar for assignments of course

    character (len = *) :: str
    str = somevariable

instead of

    str = somevariable%toString()

This is, of course, related to type casting. Implicit type casting, if I am not mistaken.

I am not looking to start a long discussion on this, as I have little more to contribute than what I have already written.


Solution

  • Fortran doesn't have a strong concept of "if object of type Y is given where one of type X is expected, convert it to one of type X". This covers different types of numeric objects (Y double precision, X real, say) as much as derived types and strings.

    You'll see this in things like argument mismatches in procedure references and so on.

    We do have type conversion in some cases, like in x+y where those two objects are of different types.

    So, a reference like somevariable is always1 a reference to that object, rather than any one (or more) of its components or type-bound procedures/binding names.

    In something like

    call dosomething(somevariable)
    

    where dosomething expects a character but somevariable is not a character, there can never be an implicit conversion carried out for you.

    However, in the two cases you are interested in, there are means to say instead that somevariable as a derived type is expected, but that something else happens instead of immediately processing that type.

    1. For data transfer, defined input/output can be used
    2. For assignment to a character, defined assignment can be used

    For example (see other questions and answers for precise explanations), we can define a type t with a generic write(formatted) and an interface for assignment(=) which has a character left-hand side and type(t) right-hand side:

    module mod
      type t
         integer :: i=0
       contains
         procedure :: write
         generic :: write(formatted) => write
      end type t
    
      interface assignment(=)
         module procedure assign
      end interface assignment(=)
      
    contains
    
      subroutine write(dtv, unit, iotype, v_list, iostat, iomsg)
        class(t), 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) "Object says hello"
      end subroutine write
    
      subroutine assign(lhs, rhs)
        character(:), allocatable, intent(out) :: lhs
        class(t), intent(in) :: rhs
    
        lhs = "From assignment"
      end subroutine assign
    
    end module mod
    

    The defined output procedure can be called with the dt edit descriptor (or using list-directed output), and the defined assignment as usual:

      use mod, only : t, assignment(=)
      character(:), allocatable :: str
    
      print '(dt)', t()
    
      str = t()
      print '(A)', str
    end
    

    1 The reference to a derived type object can be expanded to its component order in intrinsic data transfer of the object when those components are all accessible.