generics icon indicating copy to clipboard operation
generics copied to clipboard

Same Type As Requirement

Open everythingfunctional opened this issue 5 years ago • 5 comments
trafficstars

One thing that seems to come up frequently in generics (especially with what seems to be the quintessential example for Fortran), is specifying a "Trait" (or a "template" procedure) in such a way as two (or more) arguments must have the same type. For example, if I want to define a trait that requires the add function, in (psuedo)code, I'd like to have it look something like the following:

trait addable
  function add(x, y) result(z)
    type(addable), intent(in) :: x
    type(typeof(x)), intent(in) :: y
    type(typeof(x)) :: z
  end function
end trait

but (AFAIK), typeof only works for variables with a declared, specific type. I think it would make sense to have it work in this context, but it's something we should be thinking about.

everythingfunctional avatar Oct 22 '20 21:10 everythingfunctional

@everythingfunctional I don't think you need to be so pessimistic on this front. The requirement your are describing is an important one even without traits.

Use cases will matter though. For instance what is it about x, y and z being the same type that is important here? To examine this we need some additional pseudo syntax to relate the trait back to the actual template. (First I had to go find a more complicated Rust example to see what happens when the trait involved additional types.) Consider the following as a generalization of what I understand from your example

trait addable<Tx,Ty, Tz>   ! Encapsulate the aspect that Tx's and Ty's can be added to produce Tz's
  function add(x, y) result(z)
    type(Tx), intent(in) :: x
    type(Ty), intent(in) :: y
    type(Tz) :: z
  end function
end trait

template  subroutine  s<Tx,Ty,Tz>(x)
    trait(addable) :: Tx
    type(Tx), intent(in) :: x

    type(Ty) :: tmp_y
    type(Tz) :: tmp_z
    ...
end subroutine

The case where all 3 type parameters are the same type is then just a special case. But the new template will support

  • int + int
  • int + real
  • int + complex
  • real + complex and with the order swapped and with all variant kinds.

One of the abstract lessons I have learned with Magne is that one should not be stingy with type parameters. When you think two types "must" be the same, it is really just a relation between them that is screaming to get out. (E.g., the ability to add in this case.) I envision TYPEOF and CLASSOF will become relatively rare in advanced templates. But still absolutely necessary on occasion.

PS the new TYPEOF does not require the additional TYPE. In your example above, z would be declared as TYPEOF(x) :: z

tclune avatar Oct 23 '20 13:10 tclune

This is a bit tangential, but I thought some more about this issue. In the add example, there are two possibilities. The first is that this is a type-bound procedure. The second is that this is a regular procedure. Unfortunately with Fortran, both cases have the same interface and the difference is in how the interface is accessed. So is the specified trait a promise that the type has a type-bound procedure named "add" , or that there is a publicly accessible procedure of that name

Unfortunately, I think we really need to support both cases, and therefore will need to invent some sort of notation to make the distinction.

tclune avatar Oct 23 '20 17:10 tclune

Having special trait syntax to ensure that two or more dummy arguments are the same type seems a bit overkill. I would think that declaring the dummy arguments (and/or function result) with the same template type parameter will be a more common use case (and it will ensure that the two objects are the same type).

For example,

function add < T > (x, y) result(z) type(T), intent(in) :: x type(T), intent(in) :: y type(T) :: z end function

But there might be a legitimate concern on T being a valid type for add(). And what constitutes a valid type could be spelled out in a "trait" syntax.

Edit: Had to add some space to < T > to get it to show up in the saved message.

mleair avatar Oct 23 '20 20:10 mleair

I thought the idea was to avoid discussing syntax at this stage. In any case it is ambiguous where you want to define the "trait", in the generic or in the context of instantiation. I can see two ways of defining the "trait" in the generic. In Fortranesque pseudocode as a type attribute

module uses_add( T )
    type :: T
    contains
        procedure(adder) :: add
    end type T
    abstract interface
        function adder(x, y) result(z)
            type(T), intent(in) :: x, y
            type(T) :: z
        end function adder
    end interface
    ...
end module uses_add

or as a separate procendure

module uses_add( T, add )
    type :: T
    end type T
    abstract interface
        function add(x, y) result(z)
            type(T), intent(in) :: x, y
            type(T) :: z
        end function add
    end interface
    ...
end module uses_add

If you really want to use typeof

module uses_add( T )
    type :: T
    contains
        procedure(adder) :: add
    end type T
    abstract interface
        function adder(x, y) result(z)
            type(T), intent(in) :: x
            typeof(x), intent(in) :: y
            typeof(x) :: z
        end function adder
    end interface
    ...
end module uses_add

wclodius2 avatar Oct 23 '20 23:10 wclodius2

@wclodius2 Yes - the approach you suggest is pretty much what Magne and I had been considering in our discussions.

tclune avatar Oct 24 '20 12:10 tclune