j3-fortran / fortran_proposals

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

Protected Components #156

Open certik opened 4 years ago

certik commented 4 years ago

As part of #155, the committee is looking at protected components as part of

US 19 Protected Components

https://j3-fortran.org/doc/year/18/18-265.txt https://j3-fortran.org/doc/year/19/19-135r1.txt https://j3-fortran.org/doc/year/19/19-161.txt https://j3-fortran.org/doc/year/19/19-214r1.txt https://j3-fortran.org/doc/year/20/20-106.txt https://j3-fortran.org/doc/year/20/20-121.txt

certik commented 4 years ago

@zjibben can fill in the details.

It looks like the latest version so far has a requirement that a protected component (meaning one cannot write into it from outside the class) will require that one cannot assign the class variable to another (a = b), or allocate it or deallocate it, unless one writes lots of boilerplate code (custom initializer? and finalizer and custom = operator and the user must import it).

I am personally against that, I don't think that's a good approach. If using the equivalence of = being "copy constructor in C++", allocate being "constructor" and "deallocate" being "destructor", those in C++ can be implicitly generated by the compiler, or written explicitly by the user, in either case they are methods of the object itself, and thus if C++ had protected components, it seems that one could call = and create and destruct the object. I would do the same in Fortran.

klausler commented 4 years ago

C++ does have const data members that can be assigned only during construction.

zjibben commented 4 years ago

As @certik said, there is something in the works related to #16, but the existing version has many more restrictions than we expected. In particular, if you have a type with a protected component (or some subobject which ultimately has a protected component), there are many things you wouldn't be able to do outside the module where it is defined. The restrictions which raised concern were, outside the module where the type containing a protected component defined, you could not allocate or deallocate a variable of the type, nor use intrinsic assignment to the object as a whole if it contains a protected data member (a = b is disallowed if b%c is protected). It also restricted type extension, so one could never have an extension type with a protected component if the parent didn't also have a protected component (this restriction would prevent a loophole to get around the no-intrinsic-assignment rule above).

Some committee members were concerned this would make protected components too unwieldy to use in practice, that they shouldn't add more write-restrictions than private components, and protected components shouldn't have so strong an affect on how the object itself is used. They would force a lot of boiler plate to use defined assignment or type extension or allocatables if all a programmer wanted was automatic and free getter methods. On the other hand, the counter-argument is that without these restrictions you aren't really protecting the data all that much. Anyone outside the module could blow it away with a reallocation or intrinsic assignment to the variable which ultimately contains protected data.

We agreed the restriction on type extension was too far, so that is going away. The rest is up for debate. At the moment things are leaning toward reworking the requirements entirely, that the original version may be too heavy a hammer, but I think nothing is really settled one way or another.

cmacmackin commented 4 years ago

If memory serves, you can use intrinsic assignment on derived types with private components, so I don't see why anything should be different for types with protected ones.

aradi commented 4 years ago

@zjibben , @certik: Similar to @cmacmackin, I also think, that members with protected attribute should behave the same way as private members do, with the only notable difference, that reading them shall be also allowed in routines outside of the module with the type declaration.

aradi commented 4 years ago

Reading through the attached documents I got the impression, that the protected attribute should receive even more "protection" as private components (e.g. protected components can never change, unless a routine in the module defining the type is invoked). I think, this is unnecessarily strict, as not even private members have that protection:

module testmod
  implicit none

  type :: mytype_t
    private
    integer :: val = 0
  contains
    procedure :: setval => mytype_setval
    procedure :: getval => mytype_getval
  end type mytype_t

contains

  subroutine mytype_setval(this, newval)
    class(mytype_t), intent(inout) :: this
    integer, intent(in) :: newval

    this%val = newval

  end subroutine mytype_setval

  function mytype_getval(this) result(val)
    class(mytype_t), intent(in) :: this
    integer :: val

    val = this%val

  end function mytype_getval

end module testmod

program testprog
  use testmod

  type(mytype_t) :: a, b

  call a%setval(42)
  ! print *, a%val, b%val
  print *, a%getval(), b%getval()  ! 42 0
  b = a
  ! print *, a%val, b%val
  print *, a%getval(), b%getval()  ! 42 42

end program testprog

As explained above, I'd suggest to treat protected data componenets on a similar foot as private ones, with the exception of allowing protected data components to appear as rvalue in expressions outside of the defining module. The protectedness of a data member should not cause any more restriction for the given type, as a private data member does.

aradi commented 4 years ago

As for type extension: The extended type should inherit the protected data component of the base type. (In contrast to private components, which are "invisible" in extending types). If the extending type is defined in a different module as the base type, though, it would only be able to manipulate the protected data component via routines defined in the module of the base type. (Similarly, how an extending type needs the setter routines of the base type in order to manipulate its private components.)

I may have, of course, overseen something trivial, and I am definitely lacking the experience and far-sigthedness of the committee members. But I think, that by interpreting the protected data members as just a kind of light version of private components, we could add the desired functionality (reading out values directly, without the need of getter routines) with minimal side effects.

zjibben commented 4 years ago

Thank you all for the feedback, these are exactly the points I've brought up with the committee. I sat down with them to write up a toy example to depict what I (and I think others here) would like, which includes intrinsic assignment for one. This was the example:

module foo_type

  implicit none

  type :: foo
    real, protected :: a
    real, private :: b
  contains
    procedure :: init
  end type foo

contains

  subroutine init(this, x)

    class(foo), intent(out) :: this
    real, intent(in) :: x

    a = x
    b = -x

  end subroutine init

end module foo_type

program main

  use foo_type
  implicit none

  type(foo) :: f, g
  real :: x

  x = 1
  call g%init(x)

  f = g ! We want to be able to do this.

  ! Now it is not possible to insert garbage in f%a,
  ! without changing f%b

end program main

The counter-example which shows the danger they're concerned about is here, using a linked list:

! Linked list mock-up
module bar_type

  implicit none

  type :: bar
    type(bar), pointer, protected :: prev => null()
    type(bar), pointer, protected :: next => null()
  contains
    procedure :: init
  end type bar

contains

  subroutine init(this)

    class(bar), intent(out) :: this

    this%prev => this
    this%next => this

  end subroutine init

end module bar_type

program main

  use bar_type
  implicit none

  type(bar) :: f, g

  call g%init()

  f = g
  ! Now f has pointers to g instead of itself, so is a
  ! linked list that can't get back to itself.
  ! Committee solution is forbid intrinsic assignment.

end program main

The argument coming from some in the committee is that intrinsic assignment can produce things you don't want, so why do it? If you want assignment, make a defined assignment within the module where the type is defined. I might argue that this danger seems limited to linked-list type situations and not particularly damaging (existing data is intact, new object is undesired). So, we might instead have a programmer write defined assignment to override (=) when they want to prevent intrinsic assignment rather than make it impossible for all the objects where it's actually preferred. But at this point I believe we are at an impasse of conflicting interests, and most likely the "forbid intrinsic assignment, allocation, and deallocation" version will go through.

cmacmackin commented 4 years ago

Exactly the same thing could happen if you used private components for next and prev though, and intrinsic assignment is allowed for that. I do not understand where this argument is coming from and what possible justification there could be for treating private and protected components differently.

nncarlson commented 4 years ago

It seems that the proposal attempts to take "protected" absolutely literally. I'll just echo what everyone else is saying, that we want something just a bit less restrictive than private, namely it is readable outside the module where the type is declared. What is being proposed is pretty useless in my view.

zjibben commented 4 years ago

It seems that the proposal attempts to take "protected" absolutely literally.

This is exactly the problem as I see it, and as a result it looks like we're getting something that protects data so tightly that it can't be used for much. There are workarounds, like having defined-assignment which just turns around and does the intrinsic assignment within the module where the type is defined. But at that point, it's a lot of boiler plate for what it's giving you.

As a consequence of this protection, it was realized that protected is really an entirely different kind of thing than private and public. So the proposal is allowing for a private, protected component. If a type has a length-0 array component which is private, protected, you effectively just disable intrinsic assignment for that object without expanding the visible API or memory footprint. I struggle to imagine the applicability, but I'm told there have been requests for such a feature.

At any rate, my reading of the committee is this will go a very different direction than what most developers (including myself) are interested in.

certik commented 4 years ago

My understanding is that the Data subcommittee couldn't agree on the way forward, so decided not to put the proposal for a vote tomorrow.

aradi commented 4 years ago

Oh, that's quite sad news! Anyway, if some more arguments are needed: I think, the case of the linked list structure, which was used to justify the complicated behavior, is not a very good one. Usually, if one implements a container, one wants to hide the implementation details (whether it is a linked list or not) completely and provides iterators instead. So trying to make a linked list safe from unnoticed changes should not the object to aim for. What we should aim at instead is to protect consistency of data within one derive type instance without any kind of warranty for the "ethernal" existence of the derived type instance. (Exactly as the language already does it for private components...)

As a compromise, why not splitting the efforts into two parts?

And one last note, as pointers were mentioned: A pointer having the protected attribute should be protected the same way, as a pointer dummy argument with the intent(in) attribute would be protected in a subroutine: That means, only the pointer instance would be protected, but not necessarily the content it points to. Again, this maybe unsatisfactory, but would be IMHO much more consistent with the current language, as the one suggested by the committee members.

Anyway @certik @zjibben thanks a lot for all your efforts, and let's see, whether we still have to provide getters in twenty years....

certik commented 4 years ago

Ok, so after a long discussion in the Data subcommittee, the current proposal would require the developer of the class A with protected components to provide the type bound assignment operator, but if they do, then as the user, you can assign a = b (both of type A) and it will just work. You do not need to import the = operator manually, it will automatically come with the class A.

So I think, at least from a user perspective, you can use = naturally, this will not be a problem. As a developer, you have to write extra boilerplate code unfortunately.

certik commented 4 years ago

Finally, as @zjibben mentioned, the automatic deallocation would still work intuitively. Just explicit deallocation would not work.

Allocation would need to be done with a subroutine that the module developer provides (actually by overloading the constructor, so this would also work intuitively). (Explicit allocation from outside the module would not work.)

cmacmackin commented 4 years ago

I still don't understand what the reason is for treating protected components more strictly than private ones.

On Thu, 27 Feb 2020, 18:48 Ondřej Čertík, notifications@github.com wrote:

Finally, as @zjibben https://github.com/zjibben mentioned, the automatic deallocation would still work intuitively. Just explicit deallocation would not work.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/j3-fortran/fortran_proposals/issues/156?email_source=notifications&email_token=AB6ESPKNMCUULCU6EQQYTZ3RFADJJA5CNFSM4K3WKLZKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOENFP4BQ#issuecomment-592117254, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB6ESPNXWOR2KS72IHSTI5DRFADJJANCNFSM4K3WKLZA .

certik commented 4 years ago

I still don't understand what the reason is for treating protected components more strictly than private ones.

Unfortunately I don't understand why either... @zjibben, @klausler, @FortranFan, @everythingfunctional are sitting in the same room --- is any of you able to answer @cmacmackin's question?

zjibben commented 4 years ago

This has to do with the example above protecting pointers, and existing expectations from the term protected, I think. Though @cmacmackin had some counter-points to the pointer protection, so I can't say I fully understand either.

klausler commented 4 years ago

Visibility of components and definability of components are being treated as independent concepts, which (IMO) they are.

certik commented 4 years ago

The conclusion reached in the Data subcommittee so far is that we have to bring lots of use cases. That is, code that we would like to work with "protected". Then at our next meeting, we will sit down and see how to make them work with the proposal.

everythingfunctional commented 4 years ago

I think the initial idea expressed here was meant to be, relax the restrictions of private, without allowing for direct assignment/change of the component.

Instead the committee is defining protected as "disallow all ways of modifying the value of this component, except from procedures within the defining module".

zjibben commented 4 years ago

I just learned there is an older proposal which describes a version of protected which is much closer to what we expected here:

https://j3-fortran.org/doc/year/18/18-265.txt

There, intrinsic assignment is allowed, and allocation and deallocation aren't called out at all. I think this is the paper to reference in future use cases, along with @aradi's paper.

FortranFan commented 4 years ago

@cmacmackin wrote:

I still don't understand what the reason is for treating protected components more strictly than private ones. ..

I'm still struggling to understand the underlying technical reasons, but my understanding is this paper - https://j3-fortran.org/doc/year/19/19-135r1.txt - forms the basis for much of the rationale.

As stated in some of the recent posts above, my understanding is the "protected" attribute of derived type components as being currently for Fortran 202X refers to what can appear in a "variable definition context". It is the not accessibility relative to module scope as it is applied to PUBLIC/PRIVATE attributes.

aradi commented 4 years ago

I think, I know where some of the confusions comes from (at least mine). I have always interpreted the protected attribute in modules as a visibility between private and protected and wished to transfer it to derived type components as well. But reading Modern Fortran Explained (and probably the standard says the same) about the protected attribute:

This attribute does not affect the visibility of the variable, which must must still be public to be visible, but confers the same protection aginst modification that intent ın does for dummy arguments.

So, as @klausler pointed out, the visibility and definability are indeed independent from the view point of the current standard. And this is probably, what the committee tries to apply to the derived type components as well. (Which means, the protected attribute would be an additional one on top of the public attribute of a component).

certik commented 4 years ago

I think that's precisely it!

However, I think what most people want instead is something like private, but read only and visible.

On Thu, Feb 27, 2020, at 11:43 PM, Bálint Aradi wrote:

I think, I know where some of the confusions comes from (at least mine). I have always interpreted the protected attribute in modules as a visibility between private and protected and wished to transfer it to derived type components as well. But reading Modern Fortran Explained (and probably the standard says the same) about the protected attribute:

This attribute does not affect the visibility of the variable, which must must still be public to be visible, but confers the same protection aginst modification that intent ın does for dummy arguments.

So, as @klausler https://github.com/klausler pointed out, the visibility and definability are indeed independent from the view point of the current standard. And this is probably, what the committee tries to apply to the derived type components as well. (Which means, the protected attribute would be an additional one on top of the public attribute of a component).

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/j3-fortran/fortran_proposals/issues/156?email_source=notifications&email_token=AAAFAWBSX6GOYTOQCNMLZWDRFC6BHA5CNFSM4K3WKLZKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOENHSVWY#issuecomment-592390875, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAFAWGR2YS3HANOO4R3PFLRFC6BHANCNFSM4K3WKLZA.

aradi commented 4 years ago

In order to be in accordance with the spirit of the current standard, we just have to make sure, that protected, public components behave the same way as if they were intent(in) dummy arguments in a subroutine. However, IMO the intent(in)-ness should only apply to the component, not to the entire derived type. Therefore, direct allocation/deallocation of the protected component should be forbidden, but not the allocation/deallocation of the containing derived type instance as one unit:

module testmod
  implicit none

  type :: mytype
    public
    integer, allocatable, protected :: array(:)
  end type mytype

end module testmod

program testprog
  use testmod
  implicit none

  type(mytype), allocatable :: inst1, inst2

  ! Should be possibe since the inst1 as whole is not protected
  allocate(inst1)

  ! Should be NOT possible as it tries to manipulated directly a protected component
  allocate(inst1%array)

  ! This should be possible since assignment acts on the entire instance
  inst2 = inst1

  ! Should be NOT possible as it acts directly on a protected component
  inst2%array(1) = 9

  ! Should be possible as well
  deallocate(inst1, inst2)

end program testprog

This would probably give exactly the expected user experience and would be still in accordance with the spirit of the standard.

certik commented 4 years ago

The paper https://j3-fortran.org/doc/year/20/20-121.txt was withdrawn after a discussion and general opposition to it. A way out that was suggested is to implement protected components along the lines of the paper https://j3-fortran.org/doc/year/18/18-265.txt, which would satisfy what people in this thread expect. Then in addition, there could be a protected class, which would restrict even the assignment and other things. The protected component and protected class would be two different features.

zjibben commented 4 years ago

The committee overall seemed to understand the concerns expressed over the usability of the feature as proposed, and was very agreeable to withdrawal.

aradi commented 4 years ago

That's quite good news. I was not aware of https://j3-fortran.org/doc/year/18/18-265.txt, but it seems to formulate exactly the same idea as suggested above and would be in accordance with the original proposal on GitHub! So, there is still hope! :wink:

wclodius2 commented 4 years ago

Because the semantics of PROTECTED entities is not quite what is commonly thought, and the problems with PROTECTED components are because they are trying to match the semantics of PROTECTED entities, perhaps what is needed is a new keyword, e.g., VISIBLE, to indicate entities an components whose value can be seen, but cannot be directly modified.

zjibben commented 4 years ago

Everyone, I have a new specs & syntax paper over at #182 planned for the next meeting (Oct 12). Please take a look & comment your suggestions.

FortranFan commented 4 years ago

@zjibben, @certik, and everyone contributing here,

Kudos on great effort and collaboration.

With the protected components section of the paper, the revised paper looks good, and the comments posted here thus far appear to me to have captured all the use cases and needs and feature aspects I have come across. Nothing useful I can add at this stage.

However I am simply unable to understand the protected type, the rationale for it, its use cases, and the accompanying requirements and the specifications.

Thus an immediate question and a suggestion: will it be possible to break up the protected components and protected types into two separate work items with the committee and which can then have separate papers to develop the features? This way, if one aspect, say protected components, looks mature, it can advance without being held back by the other,