j3-fortran / fortran_proposals

Proposals for the Fortran Standard Committee
174 stars 14 forks source link

A shorthand for immutability #221

Open everythingfunctional opened 2 years ago

everythingfunctional commented 2 years ago

There is already a method of specifying immutable local values (i.e. calculate once, reference multiple times), but often results in some inconvenience/verbosity. For example

associate(val1 => (some_expr))
  associate(val2 => val1 + another_expr)
    associate(val3 => val2 + yet_another_expr)
      result = val1 + val2 * val3
    end associate
  end associate
end associate

I have seen it requested in a few places that the end associate be made optional, but I think that deserves a different keyword. I would propose let. Thus

let val1 = some_expr

would be exactly equivalent to

associate(val1 => (some_expr))
  ! following code
end associate
end ! of enclosing scope

which would simplify the example above to

let val1 = some_expr
let val2 = val1 + another_expr
let val3 = val2 + yet_another_expr
result = val1 + val2 * val3

This has the benefit of making it clear that associate is not being used to alias, and thus any following assignment to the value is prohibited (e.g. the variable is immutable). It also enables automatic types for local variables, albeit with the side-effect (IMO benefit) that they can only be assigned to once.

certik commented 2 years ago

In addition to let, we can also consider let mut for a mutable assignment, just like in Rust: https://doc.rust-lang.org/std/keyword.let.html

certik commented 2 years ago

So this proposal I think is equivalent to C++'s auto (#129):

let val1 = some_expr
let mut val2 = val1 + another_expr

is equivalent to:

const auto val1 = some_expr;
auto val2 = val1 + another_expr;

And so all my objections from #129 apply here also. Mainly that the code is actually less readable because it is not clear what type val1 is. The same objection already applies to associate. Rust allows you to optionally specify the type (let thing1: i32 = 100;), I wonder if we could do the same here.

everythingfunctional commented 2 years ago

Some languages use var for that. I somewhat prefer that to let mut, and then we can allow the optional type-spec for let, but not for var, as that would kind of defeat the point. I.e.

let [type-spec ::] val = some_expr
var var1 = expr ! var is now "declared" with the type of expr, but may also be reassigned to
klausler commented 2 years ago

The syntax that you propose is ambiguous with assignment-stmt in fixed form.

everythingfunctional commented 2 years ago

I suppose that's true, but as fixed-form is obsolescent (IMO) it would be reasonable for that feature not to be available in fixed form. Of course there are plenty of other opinions on that front.

FortranFan commented 2 years ago

@everythingfunctional writes Aug. 25, 2021, 11:22 AM EDT:

I have seen it requested in a few places that the end associate be made optional, but I think that deserves a different keyword. I would propose let.

To make "end associate be made optional" means, in my mind, to introduce an ASSOCIATE statement form to complement the ASSOCIATE block construct.

My observation remains Fortran has a long history of statement-construct pairs, from IF to WHERE.

I firmly believe ASSOCIATE statement form will be the better Fortrannesque way to introduce the concept of the original post (which is primarily about verbosity though) into the language. ASSOCIATE statement form will integrate better into the current standard the semantics to be brought in to satisfy different needs of Fortranners in terms of mutability c.f., what is mentioned upthread with mut.

I personally will be firmly opposed to introducing something like let.

FortranFan commented 2 years ago

See this and this.

certik commented 2 years ago

@FortranFan if you wouldn't mind, to keep our conversations focused, in the future can you please post more general comments into issues like #59 (and/or create new ones), and then just provide a quick link here. That way we can separate general discussion about "vision" from a technical discussion about a let feature; while at the same time keeping in mind these general issues which are connected to it.

Update: Thank you @FortranFan for opening up the other issues and linking them here!

rouson commented 2 years ago

I suppose that's true, but as fixed-form is obsolescent (IMO) it would be reasonable for that feature not to be available in fixed form.

It would also be reasonable to delete an obsolescent feature that has had a better alternative (free form) for nearly the entire lifetime of some of the younger people in this dialogue. If a generation of Fortran programmers has gone from kindergarten to service on the language standard body in the time since a better alternative entered the language, it has to be time to jettison the obsolescent feature that conflicts with a compelling new proposal. Compiler developers who must support the obsolescent feature can require a flag to enable the support for the obsolescent feature and let users know that the flag precludes the use of the newer conflicting feature.

rouson commented 2 years ago

@FortranFan writes Aug. 25, 2021, 11:22 AM EDT:

To make "end associate be made optional" means, in my mind, to introduce an ASSOCIATE statement form to complement the ASSOCIATE block construct.

My observation remains Fortran has a long history of statement-construct pairs, from IF to WHERE.

I like this idea and agree that it nicely mirrors other features.

I personally will be firmly opposed to introducing something like let.

I agree with everything you wrote except the last sentence. I like your suggestion of the associate statement better than I like let even though I originally liked let. Let's keep discussions as open as possible, especially in the early phases of considering a proposal. At least let's not draw lines in the sand on the very day a proposal is made.

everythingfunctional commented 2 years ago

To make "end associate be made optional" means, in my mind, to introduce an ASSOCIATE statement form to complement the ASSOCIATE block construct.

My observation remains Fortran has a long history of statement-construct pairs, from IF to WHERE.

I'm tempted to agree with this, and that it probably would be the more "Fortranique" way, but I don't like that it is effectively two separate features masquerading as one: create an alias, and create an immutable variable. I'd prefer if we're adding a new feature to make it clear that they are two separate things by having two different keywords. I could live with an ASSOCIATE statement, but it's not my first choice.

FortranFan commented 2 years ago

I suppose that's true, but as fixed-form is obsolescent (IMO) it would be reasonable for that feature not to be available in fixed form. Of course there are plenty of other opinions on that front.

Please see #225. If the obsolescent category were defined with some "teeth" in the standard, or an outmoded feature at issue (like fixed-form source here) were placed in a different category as retired, then an ideation phase will not be hindered similarly.

klausler commented 2 years ago

You should perhaps consider the option of limiting the scope of the immutable captured expression value to be something other than just "from here to the end of the BLOCK or executable-part". Otherwise, you'll have to come up with semantics for things like

do j=1,n
  let(x=a(j)+b(j))
end do
! can x be used here?  does it capture the last value?  what if n==0?

and

if (flag) then
  let(x=a+b)
else
  let(x=c+d)
end if
! can x be used here?

If you make the name a construct entity (19.1, 19.4) owned by the enclosing construct, if any, and finesse the scoping a little to isolate THEN parts from ELSE parts (and various cases from each other in SELECT, &c., you get the idea), you would dodge these concerns, which I think you have to worry about with your original statement of the scope having essentially an implied END ASSOCIATE before the end of the enclosing scope.

FortranFan commented 2 years ago

I don't like that it is effectively two separate features masquerading as one: create an alias, and create an immutable variable.

In order to achieve effective immutability in a local scope, Fortran offers options, 2 of which are rather convenient: a) named constant and b) intent(in), particularly with a contained procedure. Then there is the ASSOCIATE construct, as stated above. And ASSOCIATE statement is something that can complement the construct entity and it will be a good addition. Taken together, all these options would cover such a large percentage of the needs that it should be more than good enough for a perennially resource-constrained language such as Fortran.

everythingfunctional commented 2 years ago

If you make the name a construct entity (19.1, 19.4) owned by the enclosing construct

Exactly the kind of insight I look forward to seeing from this community. I like the idea.

In order to achieve effective immutability in a local scope, Fortran offers options, 2 of which are rather convenient: a) named constant and b) intent(in), particularly with a contained procedure.

I don't think these are convenient.

a) does not work for values that are not constant expressions b) is even more verbose than nested associate blocks, and harder to follow

It's not that I don't think an ASSOCIATE statement wouldn't be usable, it's that it communicates two different things to the reader at the same time. One has to parse the rhs to understand whether the variable can be assigned to later or not.

rouson commented 2 years ago

To use an old expression we don’t hear often anymore, Brad just took the words right out of my mouth. For the past ~24 hours, I’ve been thinking of expressing precisely the same sentiment. I probably would have done so already if my computer hadn’t died yesterday.

I want to be clear that what we are discussing is a much bigger point than many might realize. Working with immutable state is central to the functional programming paradigm. As Fortran has evolved to support multiple programming paradigms, some have gotten a lot more attention and explicit support than others. Object-oriented, parallel, and array programming have extensive feature sets in Fortran. Hopefully generic programming will too in Fortran 202Y.

Functional programming support is limited and little-used. Think about how rare it is to see pure procedures. And before this exchange, how many people even knew that associating with an expression defines immutable state? Those who work closely with me know that this practice has now become ubiquitous in my Fortran programming. I rarely declare local variables anymore and only when I have a specific need such as a loop index, but I also eschew loops in favor of array statements and elemental procedures whenever possible. I rather like the idea of calling out immutability in a new and more prominent way than hiding it as a special case of a feature that most people probably don’t use often.

Damian

On Wed, Aug 25, 2021 at 16:37 Brad Richardson @.***> wrote:

To make "end associate be made optional" means, in my mind, to introduce an ASSOCIATE statement form to complement the ASSOCIATE block construct.

My observation remains Fortran has a long history of statement-construct pairs, from IF to WHERE.

I'm tempted to agree with this, and that it probably would be the more "Fortranique" way, but I don't like that it is effectively two separate features masquerading as one: create an alias, and create an immutable variable. I'd prefer if we're adding a new feature to make it clear that they are two separate things by having two different keywords. I could live with an ASSOCIATE statement, but it's not my first choice.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/j3-fortran/fortran_proposals/issues/221#issuecomment-905945541, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADEANBG3G7I4P4LSF7KXCWLT6V5DHANCNFSM5CZKUNRA . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&utm_campaign=notification-email .

certik commented 2 years ago

Thank you everybody for the discussion, this is very helpful!

I think this should be prototyped in a compiler, and let's simply use it and see how it looks and feels.

FortranFan commented 2 years ago

@rouson writes Aug. 27, 2021 8:21 PM EDT:

Working with immutable state is central to the functional programming paradigm.

@rouson , perhaps you can open up a new issue thread toward better support toward the functional programming paradigm in Fortran? And perhaps also drive a comprehensive review of the needs and benefits with this paradigm in a set of domains (are they still in scientific and technical computing) with use cases and examples, even if in abstract algorithmic terms and/or with other languages? And what Fortran offers currently (there is of course PURE and ELEMENTAL, and now SIMPLE). Then consider what other facilities would bring benefits and how to integrate them all together better in Fortran.

It will be really good if attempts are not made instead to one-plus facilities like with let that comes across rather foreign to Fortran especially when you have a broader goal. One-plussing brings with it the dangers of premature optimization Knuth warned out, in this instance with language design and its coherency of course.

certik commented 2 years ago

It will be really good if attempts are not made instead to one-plus facilities like with let that comes across rather foreign to Fortran especially when you have a broader goal. One-plussing brings with it the dangers of premature optimization Knuth warned out, in this instance with language design and its coherency of course.

Good point @FortranFan. I agree that we should have a broader vision for Fortran with regards to functional programming, if we want to go that way, and then ensure that let or whatever other proposal fits well with that vision. I don't have a strong feeling either way.

FortranFan commented 2 years ago

@rouson writes Aug. 27, 2021 8:01 PM EDT:

.. Working with immutable state is central to the functional programming paradigm.. Functional programming support is limited and little-used .. I rarely declare local variables anymore ..

From what I have seen, the notion of immutability outside the contexts of named constant and INTENT(IN) function parameters (dummy arguments) is highly overrated. To quote the great Bard, it really is much ado about nothing. It's one of those theoretical computer science premises that may sound alright on paper but which in our experience falls apart the minute the "rubber hits the road."

As to not using local variables and its connection with functional programming, it surely is aspirational when it comes to good coding practices. But it's up to the library authors to aspire to it. The question then becomes what tools they require to achieve it in Fortran. However as I write above, that is a separate and broader discussion and separate from considerations of immutability.

A simple example I usually give is the Euclidean algorithm to find the greatest common divisor of two positive integers: apart from any outstanding bugs (it only had limited testing) and any aspects of a generic algorithm (any kind integer e.g., 64-bit) and style considerations, the following is a perfectly reasonable function in Fortran:

module euclid_m
contains
   elemental function gcd( m, n )
   ! Find the greatest common advisor of two positive integers
      ! Argument list
      integer, intent(in) :: m, n
      ! Function result
      integer :: gcd
      ! Local variables
      integer :: lvar, tmp
      if ( (m <= 0).or.(n <= 0) ) error stop "m,n must be positive."
      if ( m < n ) then 
         gcd = m ; lvar = n
      else
         gcd = n ; lvar = m
      end if
      loop_gcd: do
         if ( lvar == 0 ) exit loop_gcd
         tmp = gcd
         gcd = lvar
         lvar = mod( tmp, lvar )
      end do loop_gcd
   end function 
end module

The above with the simple driver program

   use euclid_m
   integer :: a, b
   a = 46332 ; b = 71162
   print *, "a = ", a, "; b = ", b
   print *, "gcd(a,b) = ", gcd(a, b), "; expected result is 26" 
end

with any current standard-conforming processor should yield:

a = 46332 ; b = 71162 gcd(a,b) = 26 ; expected result is 26

Now, keeping in mind everything around Fortran with its strong legacy of Fortran of nearly 75 years and all the great effort in its standard development with facilities toward better functional programming and the accompanying constraints imply:

  1. it simply will be insensible, not to mention nonconforming, for the dummy arguments to be anything other INTENT(IN),
  2. the use of local variables in this prime case of an Euclidean function is well-justified,
  3. there is no real use case here for the local tmp variable to be immutable.

Then consider a "textbook" case of an equivalent code in Rust that touts its drive for functional programming and which too may be the basis for the let suggestion in the original post

fn gcd(mut n: u64, mut m: u64) -> u64 {
    assert!(n !=0 && m != 0);
    while m != 0 {
        if m < n {
            let t = m;
            m = n;
            n = t;
        }
        m = m % n;
    }
    n
}

Going strictly by the number of typed characters above, sure the code in Fortran is more verbose than in Rust. However the verbosity in Fortran is inherent to its design and it's also beneficial; regardless, it's divorced from any immutability aspects here. Looking at the Rust code though, there are instead far bigger issues of significant concern:

  1. having built a major edifice around immutability with "patterns" and considerations of functional programming, it had to bring in exceptions with mut with let as well as function parameters c.f. the function prototype, "gcd(mut n: u64, mut m: u64)." That is arguably a failure in Rust when one creates a rule and has to proceed to immediately pair with an exception. This is highly objectionable in the context of Fortran and its modern journey starting with Fortran 90.
  2. Within the scope of the if construct ("if m < n") within the loop, there is hardly much of anything to be gained by having t immutable as with 'let t = m;"

Going by this, one can even argue there is hardly much of anything to be "imported" into Fortran from Rust such as let.

This is why I suggested above to take a broader view of the functional paradigm with fully worked out use cases, even if they are in abstract algorithmic terms or minimal working examples from other languages. To take some isolated code snippets and start building solutions around them is detrimental, in my opinion. It's something which has been quite problematic with several feature introductions in recent Fortran standard revisions also and which has led to suboptimal facilities and the approach is better avoided from now on.

certik commented 2 years ago

@FortranFan that is where I am coming from also, thanks for writing it up. This is a really good discussion.

I think let might be similar to const in C++, where my understanding of the culture and experience there, the main advantage is to help programmers, not the compiler. Having the code "const correct" allows the compiler to prevent the user to modify things that were not meant to be modified.

  1. having built a major edifice around immutability with "patterns" and considerations of functional programming, it had to bring in exceptions with mut with let as well as function parameters c.f. the function prototype, "gcd(mut n: u64, mut m: u64)." That is arguably a failure in Rust when one creates a rule and has to proceed to immediately pair with an exception. This is highly objectionable in the context of Fortran and its modern journey starting with Fortran 90.

I think you are onto something here, but I didn't understand your argument. Can you elaborate on this? My understanding is that Rust does not have exceptions: https://doc.rust-lang.org/book/ch09-00-error-handling.html, you have to return the error as a result, and Rust has some syntax that helps making this smooth. The Zig language follows a very similar approach I think.

rouson commented 2 years ago

@FortranFan thanks for the example. I agree that local variables can be indispensable. The original issue description by @everythingfunctional did not mention functional programming so I took us off-course by bringing it up. Apologies. I think the original use cases suffice for what was being proposed. If we can communicate more intention to the reader and to the compiler and thereby clarify code and prevent mistakes (.e.g., mistakenly modifying data), then I think those are good things in their own right, independent of the programming paradigm that inspired them.

Just in case it's not clear from earlier comments, associate ensures immutability when associating with an expression but allows for partial mutability when associating with a variable or object. Associating with a variable still leaves the variable's value mutable but not its allocation status, for example. These subtleties make using associate tricky. When I first started using it, I didn't realize that associate names do not inherit the allocatable attribute. I mistakenly expected automatic (re)allocation on assignment and that mistake cost me time in debugging. The thing I like best about let is that the programmer and the reader won't have to think about such subtleties as the value being mutable but not the allocation status.

FortranFan commented 2 years ago

I didn't understand your argument. Can you elaborate on this? My understanding is that Rust does not have exceptions

@certik, when I mentioned "exception" I only meant it as in ordinary English usage as illustrated in this quote! :-)

Beliavsky commented 2 years ago

Besides making end associate optional, an alternative would be to allow end associate to pertain to a list of named variables, so that the sample code in the OP could be written

associate(val1 => (some_expr))
  associate(val2 => val1 + another_expr)
    associate(val3 => val2 + yet_another_expr)
      result = val1 + val2 * val3
end associate (val1, val2, val3)

It would resemble the syntax deallocate(val1, val2, val3). I don't mind a single end associate statement, but having many end associate statements at the end of a program unit is so verbose and tedious that many Fortranners will use good old local variables instead.

Beliavsky commented 2 years ago

As an alternative to a bunch of end associate statements at the end of a program unit, one could introduce a statement end associate_all that terminates all associations previously defined.

Beliavsky commented 2 years ago

If end associate is made optional in general, I assume it would still be required if the associate appeared in a loop. Maybe you could make a rule that any associate in a loop created in a loop is automatically ended upon leaving the loop, but that seems tricky.

Meriipu commented 2 years ago

Besides making end associate optional, an alternative would be to allow end associate to pertain to a list of named variables, so that the sample code in the OP could be written

associate(val1 => (some_expr))
  associate(val2 => val1 + another_expr)
    associate(val3 => val2 + yet_another_expr)
      result = val1 + val2 * val3
end associate (val1, val2, val3)

It would resemble the syntax deallocate(val1, val2, val3). I don't mind a single end associate statement, but having many end associate statements at the end of a program unit is so verbose and tedious that many Fortranners will use good old local variables instead.

If verbosity from repeated statements is the issue why not just:

associate (val1 => expr1, val2 => expr2, ...)
end associate

or (with the alignment/spacing of personal preference):

associate (val1 => expr1, &
           val2 => expr2, &
           ...)
end associate

instead? Assuming there is not some syntax ambiguities to associate I overlook here or there not being anything else I have misunderstood.

I do not like the asymmetry between the 3 opening statements and the single close, and I really would not consider having many end associates to be "verbose and tedious" at all (and rather that it helps readability a bunch).

not too fond of short semi-cryptic (possibly inline) declarations like mut -- Fortran is not Rust after all.

Meriipu commented 2 years ago

Oh right, I see now that the second associate depends on the value of the first so that combining them would not work too well, sorry.

I still stand by what I said about asymmetry though.

difference-scheme commented 2 years ago

This would be an extremely important addition to the language, not only for functional programming, but also for enabling safer implementation of certain object-oriented design patterns, that are required for writing object-oriented code that is both efficient and loosely coupled.

Thumbs up for this one!

rouson commented 4 months ago

@everythingfunctional was a paper on this submitted to J3? It would be great to get some form of your proposal onto the Fortran 202Y work list at the June meeting.

everythingfunctional commented 4 months ago

@rouson , not that I know of. I could be convinced to write a paper, but it's not particularly high on my list of priorities.